Featured image of post Riverpod: How to initialize a SYNC Provider with an ASYNC Value?

Riverpod: How to initialize a SYNC Provider with an ASYNC Value?

I show you a way how to initialize a sync riverpod Provider with an async value.

Sometimes it is quite tricky to work with riverpod providers. Especially, when you want to initialize a provider with an async value.

Showing the version of your app

Here, I show you how you can structure your app to resolve this issue. In this example, we are going to display the version of the app using the package_info_plus package.

If you check the package’s documentation, you can see that reading this information returns a Future. So how can we initialize a Provider without making it a AsyncProvider?

PackageInfo packageInfo = await PackageInfo.fromPlatform();

Example

The trick is to decouple awaiting the Future and initializing the Provider. We can use the loading time before the app actually starts to fetch the necessary information. The user will probably see the splash screen for a couple of milliseconds longer, but later, we can synchronously access the data.

We can then use riverpod Overrides to set the loaded data to a provider. Let’s have a look how this can look like:

You can find the source code of this example on GitHub: https://github.com/bettercoding-dev/riverpod-sync-provider-async-value

main.dart

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_async_init/global_providers.dart';
import 'package:riverpod_async_init/version_page.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final overrides = await initGlobalProviders();

  runApp(
    ProviderScope(
      overrides: overrides,
      child: const MainApp(),
    ),
  );
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: VersionPage(),
    );
  }
}

Here, you can see that we call initGlobalProviders right before we actually run the app. The main function can be async, this means we can resolve our futures here.

Apps show a splash screen when staring. So all waiting done here will be done while the splash screen is shown.

We can then hand the overrides to the ProviderScope. This is a riverpod specific widget that contains all the providers states.

global_providers.dart

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import 'package:package_info_plus/package_info_plus.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'global_providers.g.dart';

@riverpod
PackageInfo packageInfo(PackageInfoRef ref) => throw UnimplementedError();

Future<List<Override>> initGlobalProviders() async {
  final overrides = <Override>[];

  // init package info
  final packageInfo = await PackageInfo.fromPlatform();
  overrides.add(packageInfoProvider.overrideWithValue(packageInfo));

  return overrides;
}

We use the global_providers.dart file to define our PackageInfo provider. But instead of directly assigning a value to the provider, we’ll throw an error.

The initialization is done in the initGlobalProviders function, which is then called in main.dart as we saw earlier.

We use the overrideWithValue function of the provider to instead of throwing an error provide the given value.

version_page.dart

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_async_init/global_providers.dart';

class VersionPage extends ConsumerWidget {
  const VersionPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final packageInfo = ref.watch(packageInfoProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Version Info'),
      ),
      body: ListView(
        children: [
          ListTile(
            title: const Text('App Name'),
            subtitle: Text(packageInfo.appName),
          ),
          ListTile(
            title: const Text('Package Name'),
            subtitle: Text(packageInfo.packageName),
          ),
          ListTile(
            title: const Text('Version'),
            subtitle: Text(packageInfo.version),
          ),
        ],
      ),
    );
  }
}

Finally, in the UI we can simply read the provider and then access the values without having to await anything.

Summary

In this example I showed you how to structure your app to resolve Futures before starting the app and using Overrides update a providers value at a later time.

Built with Hugo
Theme based on Stack designed by Jimmy