Search code examples
flutterriverpodflutter-go-router

cant go to the next screen with context.go of go_router when using asyncnotifier to call an api and store value in the DB


I am using an asyncnotifier to run background task of configuration and would like to use a loading screen which will get dismissed and go tho the next screen once the async is done.

Here is my code :

the asyncnotifier

@riverpod
class LoadingDataController extends _$LoadingDataController {
  List<Activite> activites = [];
  List<Devise> devises = [];

  @override
  FutureOr<void> build() async {
    await initData();
  }

  Future<void> initData() async {
    state = const AsyncValue.loading();
    await downloadData();
    await insertData();
    state = const AsyncValue.data(null);
  }

  Future<void> downloadData() async {
    devises = await ref.read(devisesRepositoryProvider).getDevises();
  }

  Future<void> insertData() async {
    for (final devise in devises) {
      await ref.read(deviseDaoProvider).insert(devise);
    }
  }
}

the loading widget:

class _LoadingScreenState extends ConsumerState<LoadingScreen> {
  @override
  void initState() {
    super.initState();
    // _loadData();
  }

  @override
  Widget build(BuildContext context) {

    final loadingDataAsyncValue = ref.read(loadingDataControllerProvider);

    return loadingDataAsyncValue.when(
      data: (_) {

        log("loadingAsyncValue: DATA");
        

        WidgetsBinding.instance.addPostFrameCallback((_) {
          log('LoadingScreen::build::data: ONE');
          context.go('/banks');
        });

        log('LoadingScreen::build:: TWO');

        return Scaffold(
          body: Center(),
        );
      },
      error: (error, stackTrace) {
        ref.invalidate(loadingDataControllerProvider);
        log("loadingAsyncValue: ERROR: $error");
        log("loadingAsyncValue: STACK: $stackTrace");
        return const Center(
          child: CircularProgressIndicator(),
        );
      },
      loading: () {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SpinKitSpinningLines(
                  color: Colors.blue,
                  size: 100,
                ),
                SizedBox(height: 20),
                ListTile(
                  contentPadding: EdgeInsets.zero,
                  title: Text(
                    "Nous sommes entrain de configurer votre application",
                    style: TextStyle(
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                        fontStyle: FontStyle.italic),
                    textAlign: TextAlign.center,
                  ),
                  subtitle: Text(
                    "Veuillez patienter un moment...",
                    style: TextStyle(
                        fontSize: 13,
                        fontWeight: FontWeight.w400,
                        fontStyle: FontStyle.italic),
                    textAlign: TextAlign.center,
                  ),
                )
              ],
            ),
          )
        );
      },
    );
  }
}

The loading screen is not getting dismissed and the next screen is never called

I was expecting the app to go to the '/banks' screen once the configurations operations are over in the asyncnotifier


Solution

  • While this may help you get what you need, there are other things in this code that is worth adjusting. I would recommend that you review your usage of the Notifier such that you are not returning void and are instead defining your state correctly.


    The proper way in Riverpod to do imperative actions such as "Routing, Showing a dialog, setState, etc" is to use the functionality of ref.listen.

    In your code you have the following in your data callback. This is incorrect.

    WidgetsBinding.instance.addPostFrameCallback((_) {
              log('LoadingScreen::build::data: ONE');
              context.go('/banks');
            });
    

    You should setup your build method to look like:

    @override
    Widget build(BuildContext context) {
      ref.listen(loadingDataControllerProvider, (prev, next){
        next.maybeWhen(
          data:(_){
              log('LoadingScreen::build::data: ONE');
              context.go('/banks');
          },
          orElse:() {},
        )
      });
    
      final loadingDataAsyncValue = ref.read(loadingDataControllerProvider);
    
    ... Rest of build ...
    
    }
    

    This will tell allow you to route to the /banks page when the data step triggers.