【问题标题】:Flutter GetX state management initial null valueFlutter GetX 状态管理初始空值
【发布时间】:2022-01-10 13:42:17
【问题描述】:

这是我尝试使用 Flutter GetX 包实现的,但无法正常工作。

我有一个 Firestore 文档,如果文档发生更改,我想调用一个 api 并将数据保持为可观察到的最新状态。

下面的代码似乎可以工作,但初始屏幕显示空错误,然后显示数据。

我不知道如何确保 fetchFirestoreUser()fetchApiData()(异步方法)在我移至主屏幕之前返回数据。

GetX StateMixin 似乎有助于解决异步数据加载问题,但我不知道如何在 firestore 文档更改时刷新 api 数据。

我不确定是否有任何其他状态管理最适合我的方案,但与其他状态管理包相比,我发现 GetX 更容易。

如果有人能告诉我如何解决这个问题,我将不胜感激,非常感谢。

身份验证控制器。

class AuthController extends SuperController {
  static AuthController instance = Get.find();
  late Rx<User?> _user;
  FirebaseAuth auth = FirebaseAuth.instance;

  var _firestoreUser = FirestoreUser().obs;
  var _apiData = ProfileUser().obs;

  @override
  void onReady() async {
    super.onReady();

    _user = Rx<User?>(auth.currentUser);
    _user.bindStream(auth.userChanges());

    //get firestore document
    fetchFirestoreUser();

    //fetch data from api
    fetchApiData();

    ever(_user, _initialScreen);

    //Refresh api data if firestore document has changed.
    _firestoreUser.listen((val) {
      fetchApiData();
    });
  }

  Rx<FirestoreUser?> get firestoreUser => _firestoreUser;

  _initialScreen(User? user) {
    if (user == null) {
      Get.offAll(() => Login());
    } else {
      Get.offAll(() => Home());
    }
  }

  ProfileUser get apiData => _apiData.value;

  void fetchFirestoreUser() async {
    Stream<FirestoreUser> firestoreUser =
        FirestoreDB().getFirestoreUser(_user.value!.uid);
    _firestoreUser.bindStream(firestoreUser);
  }

  fetchApiData() async {
    var result = await RemoteService.getProfile(_user.value!.uid);
    if (result != null) {
      _apiData.value = result;
    }
  }

  @override
  void onDetached() {}

  @override
  void onInactive() {}

  @override
  void onPaused() {}

  @override
  void onResumed() {
    fetchApiData();
  }
}

主屏幕

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
            child: Obx(() =>
                Text("username: " + AuthController.instance.apiData.username!))),
      ),
    );
  }
}

【问题讨论】:

  • 基本上,当您最初访问您的页面时,它会显示错误,然后将数据正确放置?
  • 正确,最初为空错误

标签: flutter google-cloud-firestore state-management flutter-getx flutter-packages


【解决方案1】:

说实话,我从来没有用过 GetX,所以我不太熟悉那种语法。

但是我可以从您的代码中看到,当您调用此方法时,您正在设置一些可变状态:

  fetchApiData() async {
    var result = await RemoteService.getProfile(_user.value!.uid);
    if (result != null) {
      _apiData.value = result;
    }
  }

相反,一个更强大的解决方案是让所有内容都具有反应性和不可变性。如果您使用 Riverpod,您可以通过组合提供者来做到这一点:

final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
  final authService = ref.watch(authRepositoryProvider);
  return authService.authStateChanges();
});

final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
  final userValue = ref.watch(authStateChangesProvider);
  final user = userValue.value;
  if (user != null) {
    // note: this should also be turned into a provider, rather than using a static method
    return RemoteService.getProfile(user.uid);
  } else {
    // decide if it makes sense to return null or throw and exception when the user is not signed in
    return Future.value(null);
  }
});

然后,您可以使用 ConsumerWidget观看数据:

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // this will cause the widget to rebuild whenever the auth state changes 
    final apiData = ref.watch(apiDataProvider);
    return apiData.when(
      data: (data) => /* some widget */,
      loading: () => /* some loading widget */,
      error: (e, st) => /* some error widget */,
    );
  }

注意:Riverpod 有一些学习曲线(恕我直言),因此您必须先了解如何使用它,然后才能理解此代码的工作原理。

【讨论】:

  • 谢谢你,我会试试看,让你知道,谢谢你的详细回答。
  • 我到了那里,当 Firestore 文档更改而不是 authStateChanges 时,我需要调用 api 数据,但我收到错误类型“Null”不是 bool 的子类型。下面的代码示例。
【解决方案2】:

实际上,这背后的原因是您将controller 放在您调用的同一页面中,因此在页面的起始阶段 Get.put() 调用您的controller 并且因为您正在从 API 获取数据,需要一些 seconds/milliseconds 才能获取数据,并且此时您的 Obx() 会呈现错误。为防止这种情况,您可以将一些 conditional 逻辑应用于您的代码,如下所示:

Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) : 

【讨论】:

  • 我在主屏幕上的数据比代码示例中显示的要多得多。如果 apiData 对象为空,有没有办法显示进度指示器?
  • 您的所有数据来自同一个控制器还是不同的控制器?
  • 是的,来自同一个控制器
  • 然后将所有内容包装在 Obx 中并应用相同的逻辑
猜你喜欢
  • 2021-08-20
  • 1970-01-01
  • 1970-01-01
  • 2020-11-26
  • 2021-12-10
  • 2021-10-07
  • 2022-01-21
  • 2021-11-08
  • 2021-11-01
相关资源
最近更新 更多