Post

How to Provide App Config in Riverpod

Learn how to provide app configuration when using Riverpod in Flutter applications.

How to Provide App Config in Riverpod

Typical problem

When using Riverpod in Flutter applications, you might need to provide configuration settings that can be accessed throughout your app. This is often necessary for things like API endpoints, feature flags, or other environment-specific settings.

The issue

While working on several projects, I noticed many developers resort to provide configuration using all means possible, such as:

  • Using global variables
  • Using predefined constants that you have to change manually in the code in order to change the configuration, in order to for example, switch between development and production environments
  • Using --dart-define with String.fromEnvironment('key) scattered throughout the codebase, which leads to hard-to-maintain code and potential errors caused by typos or missing definitions.
    (This is actually a good solution if dart defines are used properly - for example everything is loaded in one .dart file, but it is often not the case)

Approach that I recommend - using ProviderScope overrides

The approach I recommend is to use Riverpod’s ProviderScope overrides to provide configuration settings in a clean and maintainable way.

How to do it

  1. Create a configuration class that holds your app settings. This class can be a simple data class with the necessary fields. I recommend using freezed for immutability and easy copying, but this is not necessary.
    For example:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     import 'package:freezed_annotation/freezed_annotation.dart';
        
     @freezed
     class AppConfig with _$AppConfig {
       const AppConfig({
         required this.apiUrl,
       });
        
       @override
       final String apiUrl;
     }
    
  2. Create a provider for your configuration using Provider. Good practice is to use a Provider that throws an Exception if not overridden, so you can catch configuration issues early in development.
    1
    2
    3
    4
    5
    
     import 'package:flutter_riverpod/flutter_riverpod.dart';
        
     final appConfigProvider = Provider<AppConfig>((ref) {
       throw UnimplementedError('You forgot to override the appConfigProvider in tests or main.dart');
     });
    
  3. Override the provider in your main application using ProviderScope overrides. This allows you to provide different configurations for different environments (e.g., development, staging, production).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
     class App extends StatelessWidget {
       const App({
         required this.appConfig,
         super.key,
       });
        
       final AppConfig appConfig;
        
       @override
       Widget build(BuildContext context) {
         return ProviderScope(
           overrides: [
             // Override the appConfigProvider with the provided appConfig
             appConfigProvider.overrideWith(
               (ref) => appConfig,
             ),
           ],
           child: MaterialApp(
             title: 'My App',
             home: HomeScreen(),
           ),
         );
       }
     }
    
  4. Access the configuration in your app using the provider. You can access the configuration anywhere in your app using useProvider or ref.watch/ref.read.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     class HomeScreen extends ConsumerWidget {
       @override
       Widget build(BuildContext context, WidgetRef ref) {
         final appConfig = ref.watch(appConfigProvider);
         
         return Scaffold(
           appBar: AppBar(
             title: Text('Home Screen'),
           ),
           body: Center(
             child: Text('Api URL: ${appConfig.apiUrl}'),
           ),
         );
       }
     }
    

Conclusions

Using Riverpod’s ProviderScope overrides to provide app configuration is a clean and maintainable approach that allows you to easily switch between different configurations for different environments. This method avoids the pitfalls of global variables and scattered dart-define usage, leading to more robust and testable code. By following this pattern, you can ensure that your app’s configuration is centralized and easily manageable, making it simpler to maintain and scale your Flutter applications.

This post is copyrighted. All rights reserved by the author.