如何使用动态加载的异步数据设置多个 StateNotifierProvider?

发布于 2025-01-14 16:23:19 字数 2708 浏览 3 评论 0原文

我完全被下面的任务困住了。 来解决这些步骤

  1. 因此,我们的想法是使用 Riverpod

    在暂停应用程序时使用某种 Future async 从数据库获取数据 (显示 SomeLoadingPage() 等)

  2. 数据加载后:

    2.1 初始化多个全局 StateNotifierProvider,这些全局 StateNotifierProvider 使用其构造函数中的数据,并且可以通过更新其状态的方法进一步在整个应用程序中使用。

    2.2 然后显示 MainScreen() 和 UI 的其余部分

到目前为止,我已经尝试过类似的操作:

class UserData extends StateNotifier<AsyncValue<Map>> { // just <Map> for now, for simplicity
  UserData() : super(const AsyncValue.loading()) {
    init();
  }

  Future<void> init() async {
    state = const AsyncValue.loading();
    try {
      final HttpsCallableResult response =
      await FirebaseFunctions.instance.httpsCallable('getUserData').call();
      state = AsyncValue.data(response.data as Map<String, dynamic>);
    } catch (e) {
      state = AsyncValue.error(e);
    }}}
final userDataProvider = StateNotifierProvider<UserData, AsyncValue<Map>>((ref) => UserData());

final loadingAppDataProvider = FutureProvider<bool>((ref) async {
  final userData = await ref.watch(userDataProvider.future);
  return userData.isNotEmpty;
});
class LoadingPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return FutureBuilder(
      future: ref.watch(loadingAppDataProvider.future),
      builder: (ctx, AsyncSnapshot snap) {
        // everything here is simplified for the sake of a question
        final Widget toReturn;
        if (snap.connectionState == ConnectionState.waiting) {
          toReturn = const SomeLoadingPage();
        } else {
          snap.error != null
          ? toReturn = Text(snap.error.toString())
          : toReturn = const SafeArea(child: MainPage());
        }
        return toReturn;},);}}

我有意使用 FutureBuilder 而不是 .when() 因为将来我可能打算将 Future.wait([]) 与多个 future 一起使用

到目前为止,这有效,但是当我想在 < 内部实现某种 update() 方法时,麻烦就来了code>UserData 并收听它的变量贯穿整个应用程序。 之类的内容

  late Map userData = state.value ?? {};
  late Map<String, dynamic> settings = userData['settings'] as Map<String, dynamic>;

  void changeLang(String lang) {
    print('change');
    for (final key in settings.keys) {
      if (key == 'lang') settings[key] = lang;
      state = state.whenData((data) => {...data});
    }
  }

诸如SomeLoadingPage()

会出现在每个 changeLang() 方法调用上。简而言之: 我真的希望有几个 StateNotifierProvider 能够从内部修改其状态并从外部监听它。但是从数据库中获取初始状态,并使整个应用程序等待获取此数据并初始化这些提供程序。

I'm completely stuck with the task below.
So, the idea is to solve these steps using Riverpod

  1. Fetch data from db with some kind of Future async while pausing the app (display SomeLoadingPage() etc.)

  2. Once the data has loaded:

    2.1 initialize multiple global StateNotifierProviders which utilize the data in their constructors and can further be used throughout the app with methods to update their states.

    2.2 then show MainScreen() and the rest of UI

So far I've tried something like this:

class UserData extends StateNotifier<AsyncValue<Map>> { // just <Map> for now, for simplicity
  UserData() : super(const AsyncValue.loading()) {
    init();
  }

  Future<void> init() async {
    state = const AsyncValue.loading();
    try {
      final HttpsCallableResult response =
      await FirebaseFunctions.instance.httpsCallable('getUserData').call();
      state = AsyncValue.data(response.data as Map<String, dynamic>);
    } catch (e) {
      state = AsyncValue.error(e);
    }}}
final userDataProvider = StateNotifierProvider<UserData, AsyncValue<Map>>((ref) => UserData());

final loadingAppDataProvider = FutureProvider<bool>((ref) async {
  final userData = await ref.watch(userDataProvider.future);
  return userData.isNotEmpty;
});
class LoadingPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return FutureBuilder(
      future: ref.watch(loadingAppDataProvider.future),
      builder: (ctx, AsyncSnapshot snap) {
        // everything here is simplified for the sake of a question
        final Widget toReturn;
        if (snap.connectionState == ConnectionState.waiting) {
          toReturn = const SomeLoadingPage();
        } else {
          snap.error != null
          ? toReturn = Text(snap.error.toString())
          : toReturn = const SafeArea(child: MainPage());
        }
        return toReturn;},);}}

I intentionally use FutureBuilder and not .when() because in future i may intend to use Future.wait([]) with multiple futures

This works so far, but the troubles come when I want to implement some kind of update() methods inside UserData and listen to its variables through the entire app. Something like

  late Map userData = state.value ?? {};
  late Map<String, dynamic> settings = userData['settings'] as Map<String, dynamic>;

  void changeLang(String lang) {
    print('change');
    for (final key in settings.keys) {
      if (key == 'lang') settings[key] = lang;
      state = state.whenData((data) => {...data});
    }
  }

SomeLoadingPage() appears on each changeLang() method call.

In short:
I really want to have several StateNotifierProviders with the ability to modify their state from the inside and listen to it from outside. But fetch the initial state from database and make the intire app wait for this data to be fetched and these providers to be initilized.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

长梦不多时 2025-01-21 16:23:19

所以,我想我想出了如何解决这个问题:

final futureExampleProvider = FutureProvider<Map>((ref) async {
  final HttpsCallableResult response =
  await FirebaseFunctions.instance.httpsCallable('getUserData').call();

  return response.data as Map;
});

final exampleProvider = StateNotifierProvider<Example, Map>((ref) {

  // we get AsyncValue from FutureNotifier
  final data = ref.read(futureExampleProvider);
  // and wait for it to load
  return data.when(
    // in fact we never get loading state because of FutureBuilder in UI
    loading: () => Example({'loading': 'yes'}),
    error: (e, st) => Example({'error': 'yes'}),
    data: (data) => Example(data),
  );
});
class LoadingPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return FutureBuilder(
      // future: ref.watch(userDataProvider.future),
      future: ref.watch(futureExampleProvider.future),
      builder: (ctx, AsyncSnapshot snap) {
        final Widget toReturn;
        if (snap.data != null) {
          snap.error != null
          ? toReturn = Text(snap.error.toString())
          : toReturn = const SafeArea(child: MainPage());
        } else {
          // this is the only 'Loading' UI the user see before everything get loaded
          toReturn = const Text('loading');
        }
        return toReturn;
      },
    );
  }
}
class Example extends StateNotifier<Map> {
  Example(this.initData) : super({}) {
    // here comes initial data loaded from FutureProvider
    state = initData;
  }
  // it can be used further to refer to the initial data, kinda like cache
  Map initData;

  // this way we can extract any parts of initData
  late Map aaa = state['bbb'] as Map

  // this method can be called from UI
  void ccc() {
    
    // modify and update data
    aaa = {'someKey':'someValue'};
    // trigger update
    state = {...state};

  }
}

这对我有用,至少在这个复杂程度上是这样。
如果有更好的建议,我将保留未解决的问题。

So, I guess I figured how to solve this:

final futureExampleProvider = FutureProvider<Map>((ref) async {
  final HttpsCallableResult response =
  await FirebaseFunctions.instance.httpsCallable('getUserData').call();

  return response.data as Map;
});

final exampleProvider = StateNotifierProvider<Example, Map>((ref) {

  // we get AsyncValue from FutureNotifier
  final data = ref.read(futureExampleProvider);
  // and wait for it to load
  return data.when(
    // in fact we never get loading state because of FutureBuilder in UI
    loading: () => Example({'loading': 'yes'}),
    error: (e, st) => Example({'error': 'yes'}),
    data: (data) => Example(data),
  );
});
class LoadingPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return FutureBuilder(
      // future: ref.watch(userDataProvider.future),
      future: ref.watch(futureExampleProvider.future),
      builder: (ctx, AsyncSnapshot snap) {
        final Widget toReturn;
        if (snap.data != null) {
          snap.error != null
          ? toReturn = Text(snap.error.toString())
          : toReturn = const SafeArea(child: MainPage());
        } else {
          // this is the only 'Loading' UI the user see before everything get loaded
          toReturn = const Text('loading');
        }
        return toReturn;
      },
    );
  }
}
class Example extends StateNotifier<Map> {
  Example(this.initData) : super({}) {
    // here comes initial data loaded from FutureProvider
    state = initData;
  }
  // it can be used further to refer to the initial data, kinda like cache
  Map initData;

  // this way we can extract any parts of initData
  late Map aaa = state['bbb'] as Map

  // this method can be called from UI
  void ccc() {
    
    // modify and update data
    aaa = {'someKey':'someValue'};
    // trigger update
    state = {...state};

  }
}

This works for me, at least on this level of complexity.
I'll leave question unsolved in case there are some better suggestions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文