关键字

late

late关键字允许变量将在稍后初始化,但必须在使用之前初始化。

这与 final 关键字不同,final 关键字用于声明必须在声明时或构造函数运行之前初始化的变量。

late 关键字的主要优点是可以提高性能,尤其是在构造函数中包含复杂初始化逻辑的类的情况下。通过使用 late 关键字,您可以推迟初始化,直到实际需要使用该变量时再进行初始化。这可以避免在构造函数中执行不必要的初始化工作,从而提高性能。

以下是一些有关如何使用 late 关键字的示例:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late String _data;

  @override
  void initState() {
    super.initState();

    // 此处推迟了 _data 变量的初始化
    _loadData();
  }

  void _loadData() async {
    // 模拟异步数据加载
    await Future.delayed(Duration(seconds: 2));

    setState(() {
      _data = 'Data loaded';
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_data == null) {
      return CircularProgressIndicator();
    }

    return Text(_data);
  }
}

在这个示例中,_data 变量使用 late 关键字声明。这意味着该变量不必在声明时或构造函数运行之前初始化。相反,它可以在稍后初始化,例如在 initState 方法中。这可以提高性能,因为只有在实际需要使用该变量时才会进行初始化。

请注意,late 关键字只能用于非空类型。这意味着 late 变量不能为 null。如果您需要声明可能为 null 的变量,则可以使用 ? 可空性操作符:late String? _data;

final

在 Dart 中,final 关键字用于定义一个只能被赋值一次的变量。它表示该变量的值在被第一次赋值后不可再更改。这对于创建常量或不希望被重新赋值的变量非常有用。

使用 final 的场景

  1. 局部变量:你可以在函数内部使用 final 来定义局部变量。
  2. 类成员变量:你可以在类中使用 final 来定义成员变量。

局部变量

void main() {
  final name = 'Alice';
  // name = 'Bob'; // 错误:name 已经被赋值,不能再次赋值
  print(name);
}

类成员变量

class Person {
  final String name;
  final int age;

  Person(this.name, this.age);
}

void main() {
  final person = Person('Alice', 30);
  // person.name = 'Bob'; // 错误:name 是 final 变量,不能修改
  print('${person.name}, ${person.age}');
}

finalconst 的区别

  • final:变量的值只能被赋值一次,但它的值在运行时确定。例如,final 可以用于构造函数参数。
  • const:变量的值在编译时确定,并且是编译时常量。const 用于定义编译时常量,而 final 只能确保在运行时赋值一次。

变量和函数前的_

在 Dart 中,变量或函数名前的下划线(_)通常用于表示该成员是私有的。

但是,重要的是要注意,Dart 中没有真正的私有成员。下划线只是约定,通常由程序员遵循来表明成员不应该从外部类或模块访问。

async和await

async 关键字用于声明一个函数是异步的。异步函数返回一个 Future 对象,表示该函数将在未来某个时间点完成。

await 关键字用于等待一个异步操作完成,并获取其结果。await 只能在 async 函数内部使用。

import 'dart:async';

Future<String> fetchUserData() async {
  // 模拟网络请求
  await Future.delayed(Duration(seconds: 2));
  return 'User data';
}

void main() async {
  print('Starting...');
  String userData = await fetchUserData();
  print('Fetched data: $userData');
  print('Finished.');
}

try,catch,finally

try {
  // 可能抛出异常的代码
} catch (e) {
  // 处理异常
} finally {
  // 无论是否发生异常都会执行的代码
}

异步编程

使用Future实现

Future 是 Dart 的一种核心概念,用于处理异步操作。当你有一个 Future,你不能立即得到它的结果,因为它可能还没有完成。你需要等待 Future 完成,或者注册一个回调函数,在 Future 完成时调用

和Future有关的方法

Future.value

返回一个future对象

Future<String> fetchData() {
  return Future.value('Data from server');
}

Future.then

等待future完成之后进行操作

Future.catchError

方法用于捕获和处理 Future 中的错误

Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () {
    return 'Data from server';
  });
}

void main() {
  fetchData().then((data) {
    print('Data received: $data');
  }).catchError((error) {
    print('Error: $error');
  });
}

Future.whenComplete

whenComplete 方法用于在 Future 完成(无论成功还是失败)后执行回调函数。

Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () {
    return 'Data from server';
  });
}

void main() {
  fetchData().then((data) {
    print('Data received: $data');
  }).catchError((error) {
    print('Error: $error');
  }).whenComplete(() {
    print('Future completed');
  });
}

Future.wait

Future.wait 方法用于等待多个 Future 全部完成,并返回它们的结果。

Future<String> fetchData1() {
  return Future.delayed(Duration(seconds: 2), () {
    return 'Data 1 from server';
  });
}

Future<String> fetchData2() {
  return Future.delayed(Duration(seconds: 3), () {
    return 'Data 2 from server';
  });
}

void main() {
  Future.wait([fetchData1(), fetchData2()]).then((List<String> results) {
    print('Data received: ${results[0]}, ${results[1]}');
  }).catchError((error) {
    print('Error: $error');
  });
}

Future.forEach

Future.forEach 方法用于对集合中的每个元素执行异步操作。

Future<void> processItems(List<int> items) {
  return Future.forEach(items, (int item) async {
    await Future.delayed(Duration(seconds: 1));
    print('Processed item: $item');
  });
}

void main() {
  processItems([1, 2, 3]).then((_) {
    print('All items processed');
  }).catchError((error) {
    print('Error: $error');
  });
}

Future实现异步编程的原理

Future 是 Dart 语言中用于处理异步操作的核心机制。它的实现原理基于事件循环(Event Loop)和消息队列(Message Queue)。以下是 Future 实现异步的基本原理:

事件循环(Event Loop)

Dart 是单线程语言,但它通过事件循环机制实现了异步编程。事件循环是一个无限循环,负责处理事件和消息。

  1. 事件循环的启动:当 Dart 程序启动时,事件循环开始运行。
  2. 消息队列:事件循环从消息队列中取出消息并处理。消息队列中包含各种事件,如用户输入、网络请求、定时器等。

Future 的工作原理

  1. 创建 Future:当你创建一个 Future 时,实际上是将一个任务(回调函数)添加到消息队列中。

    Future<String> fetchData() {
      return Future.delayed(Duration(seconds: 2), () {
        return 'Data from server';
      });
    }
    

    在这个例子中,Future.delayed 创建了一个 Future,并在2秒后将回调函数添加到消息队列中。

  2. 事件循环处理:事件循环在处理完当前任务后,会从消息队列中取出下一个任务并执行。

  3. 回调函数执行:当事件循环处理到 Future 的回调函数时,回调函数会被执行。如果回调函数返回一个值,这个值会被包装成一个 Future 对象。

  4. 完成 Future:回调函数执行完毕后,Future 被标记为完成,并触发 then 方法中的回调函数。

    fetchData().then((data) {
      print('Data received: $data');
    });
    

    在这个例子中,then 方法注册的回调函数会在 Future 完成后被调用。

异步操作的非阻塞特性

由于事件循环和消息队列的存在,Dart 的异步操作不会阻塞主线程。当一个异步操作(如网络请求)开始时,事件循环可以继续处理其他任务,而不是等待异步操作完成。这使得 Dart 程序能够保持响应性。

总结

Future 实现异步的原理基于事件循环和消息队列。通过将任务添加到消息队列中,事件循环可以在处理完当前任务后,继续处理其他任务,从而实现非阻塞的异步操作。这种机制使得 Dart 程序能够高效地处理异步任务,保持响应性。

下面这一段来源于博客:作者:GitLqr 链接:https://juejin.cn/post/6949898044628271140 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

事件循环机制

对于用户点击, 滑动, 硬盘 IO 访问等事件, 你不知道何时发生或以什么顺序发生, 所以得有一个永不停歇且不能阻塞的循环来等待处理这些 “突发” 事件. 于是, 基于 事件循环机制单线程模型 就出现了:

img

Dart 事件循环机制由 一个消息循环(Event Looper) 和 两个消息队列(Event Queue) 构成, 这两个消息队列分别是: 事件队列(Event queue)微任务队列(MicroTask queue).

Event Looper

Dart 在执行完 main 函数后, Event Looper 就开始工作, Event Looper 优先全部执行完 Microtask Queue 中的 event, 直到 Microtask Queue 为空时, 才会执行 Event Looper 中的 event, Event Looper 为空时才可以退出循环.

注意: Event Looper 为空时, 是 可以 而不是 一定 要退出, 视场景而定.

img

Event Queue

Event Queue` 的 event 来源于 `外部事件` 和 `Future
  • 外部事件: 例如输入/输出, 手势, 绘制, 计时器, Stream 等
  • Future: 用于自定义 Event Queue 事件

对于外部事件, 一旦没有任何 microtask 要执行, Event loop才会考虑 event queue中的第一项,并且将会执行它.

通过 Future 实例向 Event Queue 添加事件:

Future(() {
  // 事件任务
});

Microtask Queue

  • Microtask Queue 的优先级高于 Event Queue.
  • 使用场景: 想要在稍后完成一些任务(microtask) 但又希望在执行下一个事件(event)之前执行.

Microtask 一般用于非常短的内部异步动作, 并且任务量非常少, 如果微任务非常多, 就会造成 Event Queue 排不上队, 会阻塞 Event Queue 的执行(如: 用户点击没有反应). 所以, 大多数情况下优先考虑使用 Event Queue, 整个 Flutter 源代码仅引用 scheduleMicroTask() 方法 7 次.

通过 scheduleMicroTask() 函数向 Microtask Queue 添加任务:

scheduleMicrotask(() {
  // 微任务
});

使用Stream实现

Stream 是 Dart 中用于处理一系列异步数据的对象。它可以用于处理连续的数据流,如用户输入、文件读取、网络数据等。

创建 Stream

你可以使用 StreamController 来创建和管理一个 Stream

import 'dart:async';

void main() {
  // 创建一个 StreamController
  final controller = StreamController<int>();

  // 获取 Stream
  final stream = controller.stream;

  // 监听 Stream
  stream.listen((data) {
    print('Received data: $data');
  });

  // 向 Stream 添加数据
  controller.add(1);
  controller.add(2);
  controller.add(3);

  // 关闭 StreamController
  controller.close();
}

使用 Stream 生成器

你也可以使用 async*yield 关键字来创建一个 Stream

import 'dart:async';

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    await Future.delayed(Duration(seconds: 1)); // 模拟延迟
    yield i;
  }
}

void main() {
  countStream(5).listen((data) {
    print('Received data: $data');
  });
}

Stream 的实现原理

Stream 的实现原理基于事件循环(Event Loop)和消息队列(Message Queue)。当你创建一个 Stream 并添加数据时,这些数据会被放入一个内部队列中。当有监听器(listener)监听这个 Stream 时,事件循环会从队列中取出数据并传递给监听器。

使用Isolate实现

Isolate 是 Dart 的并发模型,用于在单独的线程中执行耗时任务,避免阻塞主线程。每个 Isolate 都有自己的内存和事件循环

创建和使用 Isolate

你可以使用 Isolate.spawn 方法来创建一个新的 Isolate,并使用 SendPortReceivePort 来进行通信。

import 'dart:isolate';

void isolateFunction(SendPort sendPort) {
  int result = 0;
  for (int i = 0; i < 1000000000; i++) {
    result += i;
  }
  sendPort.send(result);
}

void main() async {
  // 创建一个 ReceivePort 来接收消息
  ReceivePort receivePort = ReceivePort();

  // 创建一个新的 Isolate
  Isolate.spawn(isolateFunction, receivePort.sendPort);

  // 监听 ReceivePort
  receivePort.listen((message) {
    print('Result from isolate: $message');
  });
}

Isolate 的实现原理

Isolate 的实现原理基于 Dart 的并发模型。每个 Isolate 都有自己的内存空间和事件循环,它们之间通过消息传递进行通信。当你创建一个新的 Isolate 时,Dart 会在一个新的线程中运行这个 Isolate,并在主线程和子线程之间建立一个消息通道(SendPortReceivePort)。通过这个消息通道,你可以安全地在不同的 Isolate 之间传递数据。

总结

  • Future:适用于处理单个异步操作,基于事件循环和消息队列。
  • Stream:适用于处理连续的异步数据流,基于事件循环和消息队列。
  • Isolate:适用于处理耗时任务,基于 Dart 的并发模型和消息传递。

渲染机制

https://juejin.cn/post/6973818961724964901

三棵树:widget, element, RenderObjects树

  • Widget:Widget是Flutter的核心部分,是用户界面的不可变描述。做Flutter开发接触最多的就是Widget,可以说Widget撑起了Flutter的半边天;
  • Element:Element是实例化的 Widget 对象,通过 Widget 的 createElement() 方法,是在特定位置使用 Widget配置数据生成;
  • RenderObject:用于应用界面的布局和绘制,保存了元素的大小,布局等信息;

https://www.geekailab.com/2021/01/10/Flutter-three-tree/

组件

StatefulWidget和StatelessWidget

在 Flutter 中,StatefulWidgetStatelessWidget 是两种基本的小部件类型,用于构建用户界面。它们的主要区别在于它们如何管理状态。

无状态小部件StatefulWidget

无状态小部件没有内部状态。这意味着它们的输出完全由它们的输入和构建时提供的属性决定。无状态小部件在整个生命周期中保持不变,不会因用户交互或其他外部因素而重新渲染。

创建无状态小部件类的最简单方法是继承 StatelessWidget 类并重写 build 方法。build 方法必须返回一个 Widget,该小部件将呈现到屏幕上。

class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello, World!');
  }
}

有状态小部件StatelessWidget

有状态小部件包含内部状态,可能会随着时间的推移而改变。此状态用于控制小部件的输出。有状态小部件会在其状态发生变化时重新渲染。

要创建状态有状态小部件类,您需要继承 StatefulWidget 类并创建一个 State 类。State 类包含小部件的状态并提供以下方法:

  • initState:此方法将在小部件首次创建时调用。您可以使用它来初始化小部件的状态。
  • didChangeDependencies:此方法将在小部件的依赖项更改时调用。您可以使用它来响应其他小部件的状态变化。
  • build:此方法与无状态小部件的 build 方法相同。它用于构建小部件将呈现到屏幕上的内容。
  • setState:此方法用于更新小部件的状态。这将导致小部件重新渲染。
class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Text('Counter: $_counter');
  }

  void incrementCounter() {
    setState(() {
      _counter++;
    });
  }
}

创建继承它们的类的方法有什么不同?

创建继承 StatelessWidgetStatefulWidget 的类的方法的主要区别在于:

  • 无状态小部件 只需要重写 build 方法。
  • 有状态小部件 需要创建一个 State 类并重写 initStatedidChangeDependenciesbuildsetState 方法。

此外,有状态小部件通常需要使用 setState 方法来更新其状态。这会导致小部件重新渲染,并反映状态的变化。

何时使用无状态小部件?

  • 小部件没有内部状态。
  • 小部件的输出完全由其输入和属性决定。
  • 小部件不需要响应用户交互或其他外部因素。

示例

以下是一个无状态小部件的示例,它显示一个文本小部件,其中包含“Hello, World!”:

class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello, World!');
  }
}

以下是一个有状态小部件的示例,它显示一个计数器小部件,用户可以点击它来增加计数:

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Text('Counter: $_counter');
  }

  void incrementCounter() {
    setState(() {
      _counter++;
    });
  }
}

AppBar设计

关于appbar的参数:https://juejin.cn/post/7143181602124726308

想要做成小红书这种,左边目录,中间“关注/推荐”,右边搜索,就直接在appbar的title参数里面加入“关注/推荐”

@override
  Widget build(BuildContext context) {
    return AppBar(
      elevation: 0,
      backgroundColor: Colors.white,
      leading: IconButton(
        icon: const Icon(Icons.menu, color: Colors.blue),
        onPressed: () {
          // Scaffold.of(context).openDrawer();
        },
      ),
      title: Row(
        children: [
          const SizedBox(width: 25),
          Expanded(
            child: TabBar(
              controller: _tabController,
              tabs: widget.tabs.map((tab) => Tab(text: tab)).toList(),
              labelColor: Colors.blue,
              unselectedLabelColor: Colors.grey,
              indicatorSize: TabBarIndicatorSize.label,
              indicatorWeight: 3,
            ),
          ),
          const SizedBox(width: 25),
        ],
      ),
      actions: widget.showSearch
          ? [
              IconButton(
                icon: const Icon(Icons.search, color: Colors.blue),
                onPressed: () {
                  // 跳转到搜索页面
                },
              )
            ]
          : [],
    );
  }

Expanded:占据剩余空间

这里有一篇文章有两个对比用不用expanded:https://stackoverflow.com/questions/68539642/how-expanded-widget-works-in-flutter

个人感受就是组件expaned的高度是根据父组件的高度自适应分配,弹性布局的意思。

LayoutBuilder:获取一个组件的宽度

要获取最外层容器的宽度,你需要确保容器在布局过程中已经完成测量。在 Flutter 中,你可以使用 LayoutBuilder 小部件来获取布局约束,其中包含了容器的最大宽度。

下面是一个示例,展示了如何使用 LayoutBuilder 来获取 Container 的宽度:

Container(
  child: LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      // 获取Container的最大宽度
      double containerWidth = constraints.maxWidth;

      // 使用containerWidth进行后续操作
      // ...

      return ElevatedButton(
        // ... 其他按钮属性
        child: Stack(
          children: [
            if (_selectedOption != -1)
              Positioned(
                left: 0,
                top: 0,
                child: Container(
                  height: 20,
                  width: containerWidth * votePercentage[i], // 使用containerWidth来设置宽度
                  decoration: BoxDecoration(
                    color: _selectedOption == i
                        ? colorPercents[i + 1]
                        : colorPercents[0],
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
              ),
            Align(
              alignment: Alignment.center,
              child: Text(text[i]),
            ),
          ],
        ),
      );
    },
  ),
);

在这个例子中,LayoutBuilder 会为其子级提供一个回调函数,该函数会在布局过程完成后被调用。BoxConstraints 参数包含了子级在布局过程中的约束信息,其中 maxWidth 属性就是容器的最大宽度。

请注意,LayoutBuilder 本身并不直接参与布局,它只是提供了一个回调函数来获取布局约束。如果你需要在布局过程中动态设置容器的宽度,你可能需要使用 LayoutBuilder 来获取约束,然后根据这些约束来设置容器的宽度。

确保 LayoutBuilder 小部件是 Container 的直接子级,这样它才能正确地获取到 Container 的布局约束。如果 Container 是其他小部件的子级,你可能需要调整你的小部件树结构以确保 LayoutBuilder 可以正确地获取到 Container 的宽度。

在外层调用openDrawer()

在Flutter中,Scaffold.of(context).openDrawer() 方法是如何工作的呢?当你调用 Scaffold.of(context).openDrawer() 时,Flutter实际上是在当前的 BuildContext 中查找最近的 Scaffold 组件,并调用它的 openDrawer() 方法。

BuildContext 是一个非常重要的概念,它代表了在widget树中的位置和层次结构。当你调用 Scaffold.of(context) 时,Flutter会从当前的widget开始向上遍历widget树,直到找到最近的 Scaffold 组件。

在你的代码中,当你点击 AppBar 中的菜单按钮时,onPressed 回调函数是在 DynamicTopBar 内部定义的。但是,Scaffold.of(context).openDrawer() 方法是在 AppBar 内部调用的,它是在 DynamicTopBar 内部定义的 AppBar 小部件中调用的。

Scaffold.of(context) 方法会沿着widget树向上查找最近的 Scaffold 组件,并返回它。在这个例子中,由于 AppBarScaffold 的子部件,Scaffold.of(context) 会找到 Scaffold 并调用它的 openDrawer() 方法。

所以,Scaffold.of(context).openDrawer() 是如何工作的,是因为它在widget树中向上查找最近的 Scaffold 组件,并调用它的 openDrawer() 方法。

TabController:顶部栏内容滑动更新

通过tabController实现,可以传递这个参数进一个子组件实现控制。

import 'package:flutter/material.dart';

class DynamicTopBar extends StatelessWidget implements PreferredSizeWidget {
  final List<String> tabs;
  final bool showSearch;
  final TabController tabController; // 接受外部提供的TabController

  const DynamicTopBar({
    Key? key,
    required this.tabs,
    this.showSearch = true,
    required this.tabController,
  }) : super(key: key);

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);

  @override
  Widget build(BuildContext context) {
    return AppBar(
      elevation: 0,
      backgroundColor: Colors.white,
      leading: IconButton(
        icon: Icon(Icons.menu, color: Colors.blue[700]),
        onPressed: () {
          Scaffold.of(context).openDrawer();
        },
      ),
      title: Row(
        children: [
          const SizedBox(width: 25),
          Expanded(
            child: TabBar(
              controller: tabController, // 使用传入的TabController
              tabs: tabs.map((tab) => Tab(text: tab)).toList(),
              labelColor: Colors.blue[700],
              labelStyle: const TextStyle(
                fontSize: 20,
                fontFamily: 'SimHei',
                fontWeight: FontWeight.bold,
              ),
              unselectedLabelColor: Colors.grey[400],
              indicatorSize: TabBarIndicatorSize.label,
              indicatorWeight: 3,
              indicatorColor: Colors.blue[700],
            ),
          ),
          const SizedBox(width: 25),
        ],
      ),
      actions: showSearch
          ? [
              IconButton(
                icon: Icon(Icons.search, color: Colors.blue[700]),
                onPressed: () {
                  // 跳转到搜索页面
                },
              )
            ]
          : [],
    );
  }
}

页面逻辑

前端如何判断用户是否登录

在Flutter中,常见的存储和管理JWT的方法包括使用shared_preferences插件来在本地存储和检索JWT,并在需要时进行验证。以下是详细的步骤和示例代码:

1. 添加依赖

首先,需要在pubspec.yaml文件中添加shared_preferences依赖:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.9

2. 存储JWT

当用户登录成功后,可以将后端返回的JWT存储在本地存储中:

import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveToken(String token) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('authToken', token);
}

3. 检查用户是否登录

在“我的”界面中,可以通过检查本地存储中的JWT来判断用户是否已经登录:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  bool isLoggedIn = false;

  @override
  void initState() {
    super.initState();
    checkLoginStatus();
  }

  Future<void> checkLoginStatus() async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString('authToken');

    if (token != null) {
      // 这里可以进一步验证token的有效性
      setState(() {
        isLoggedIn = true;
      });
    } else {
      setState(() {
        isLoggedIn = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我的页面'),
      ),
      body: Center(
        child: isLoggedIn ? LoggedInWidget() : LoggedOutWidget(),
      ),
    );
  }
}

class LoggedInWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('欢迎回来,用户已登录');
  }
}

class LoggedOutWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('请登录');
  }
}

4. 清除JWT(用户登出)

当用户登出时,可以清除本地存储中的JWT:

Future<void> logout() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('authToken');
}

通过以上步骤,您可以在Flutter应用中存储JWT并在需要时检查用户的登录状态。这样,无论是安卓还是iOS,您都可以确保用户的登录状态得到正确的管理和验证。