A new Flutter template project using the Riverpod library for state management.
- Setup
- Guide to Run Code
- API Used
- Features
- Run Configuration Guide
- Coding Guide
- Release Guide
- APIs
- Riverpod Library Guide
- FAQ
- DO/DON'T
- TODOs
- Flutter SDK installed - Current tested flutter SDK 3.19.6
- Android Studio / VS Code installed
- Emulator / Simulator / Physical device for testing
- Clone project
- Run
flutter pub get
- Generating Code - To generate the necessary code, use the following commands:
One-time generation:
dart run build_runner build --delete-conflicting-outputs
Continuous watch:
dart run build_runner watch --delete-conflicting-outputs
- Guide: Mocking Providers
- Use mock run configuration to use mock/hard-coded data TODO
- Use actual configuration run actual API data
- Base URL:
https://api.github.com/
- Users:
- User Repositories:
https://api.github.com/users/:username/repos
- User Repositories:
- Endpoint:
users/dinkar1708/repos?per_page=3
API-TYPE: No API
Requirement: Maintain widget local state only without network operation
How to Use Riverpod in This Case: Use flutter hooks HookWidget widget
- Define parent class:
// see parent class
@RoutePage()
class CounterPage extends HookWidget {
- Use notifier provider on UI
// define
final counterState = useState(0);
// use variable
Text(
'Value ${counterState.value}',
style: AppTextStyle.labelMedium
.copyWith(color: context.color.textPrimary),
),
// modify variable
onPressed: () {
counterState.value = counterState.value + 1;
},
API-TYPE: GET
Requirement: Fetch Data from Network
How to Use Riverpod in This Case: User Future Provider - https://riverpod.dev/docs/providers/future_provider
- Define a future and perform a network call:
@riverpod
class RepositoryListNotifier extends _$RepositoryListNotifier {
// below is the future provider
@override
Future<List<RepositoryListModel>> build() async => await ref
.read(userRepositoryProvider)
.getRepositories(userName, pageSize);
}
- Define parent class:
// see parent class
class RepositoryListPage extends ConsumerStatefulWidget {
- Use notifier provider on UI
final repositoryListAsync = ref.watch(repositoryListNotifierProvider);
return switch (repositoryListAsync) {
AsyncError(:final error) =>
SliverToBoxAdapter(child: Text('Error $error')),
AsyncData(:final value) => _buildListView(value),
_ => const SliverToBoxAdapter(child: Center(child: Text('Loading...'))),
};
API-TYPE: GET
Requirement: Fetch Data from Network and handle widget local state(clear button in search box)
How to Use Riverpod in This Case: User Future Provider with hooks - https://riverpod.dev/docs/essentials/side_effects#going-further-showing-a-spinner--error-handling
- Define a future and perform a network call:
// TODO later
- Define parent class:
// see parent class is StatefulHookConsumerWidget which is special using via 'hooks_riverpod' library
// task 1 can use useState final isSearchingNotifier = useState(false);
// task 2 can do read and watch - final usersListAsync = ref.watch(usersNotifierProviderProvider);
class UsersPage extends StatefulHookConsumerWidget {
- Use notifier provider on UI
// use widget local state variable
final isSearchingNotifier = useState(false);
// Change the value
onChanged: (value) {
isSearchingNotifier.value = true;
},
// load user list data
final usersListAsync = ref.watch(usersNotifierProviderProvider);
return switch (usersListAsync) {
AsyncError(:final error) => SliverToBoxAdapter(
child: SliverToBoxAdapter(child: Text('Error $error'))),
AsyncData(:final value) => _buildListView(value),
_ => const SliverToBoxAdapter(child: Center(child: Text('Loading...'))),
};
API-TYPE: POST
Requirement: Send data to network
How to Use Riverpod in This Case:
To use the Flutter Future Notifier Provider, we can follow the guidelines provided in the Riverpod documentation. For a more comprehensive guide on handling side effects, such as showing a spinner and error handling, refer to this section of the Riverpod documentation. However, for simplicity and to maintain clean code, you can handle error messages and loading states using the FutureProvider in Riverpod.
- Define a future and perform a network call:
@riverpod
class LoginNotifier extends _$LoginNotifier {
@override
Future<LoginStateModel> build() async {
debugPrint('login initial state....');
return Future.value(const LoginStateModel());
}
- Define parent class:
// see parent class
class LoginPage extends ConsumerStatefulWidget {
- Use notifier provider on UI // use to handle progress indicator
// watch all the times
final loginState = ref.watch(loginNotifierProvider);
// use on UI with condition
child: loginState.value?.apiResultState == APIResultState.loading
? const CircularProgressIndicator()
: const Text('Login'),
// use to show error message
// use error message on UI
const SizedBox(
height: 100,
),
if (loginState.value?.errorMessage != null)
Text(loginState.value!.errorMessage),
// use to do network request
// call api and handle error such as snack bar or alert etc.
loginNotifier.login(loginRequestModel).then((loginStateModel) => {
if (loginStateModel.apiResultState == APIResultState.result &&
loginStateModel.loginResponseModel != null)
{
showSnackBar(context,
'Login success ${loginStateModel.loginResponseModel!.userName}'),
context.router.replaceAll([HomeRoute(title: 'Home')]),
}
else
{
// show error message as snack bar or dailog anything
showSnackBar(
context, 'Login failed ${loginStateModel.errorMessage}'),
}
});
Loading State after the button is clicked
Loaded State after the API call is done
API-TYPE: No api
Requirement: Manage complext widget local state
How to Use Riverpod in This Case: State notifier provider https://riverpod.dev/docs/providers/state_notifier_provider
- Define state model class StateModel {
}
- Define a state notifier and define initial state:
@riverpod
class ABCNotifier extends _$ABCNotifier {
@override
// note here not using future
StateModel build() async {
return StateModel();
}
- Use notifier provider on UI
//
Define the parent StatefulHookConsumerWidget
, which allows you to use useState
and ref
to access the provider in the desired manner.
Examples:
-
POST + Local State: Follow the login example, but define the parent
StatefulHookConsumerWidget
to useuseState
. -
POST + GET: Follow the login example and the repository list example.
-
POST + GET + Widget Local State: Follow the login example, the repository list example, and define the parent as
StatefulHookConsumerWidget
to useuseState
.
This section provides guidance on setting up flavors, run configurations, and build modes for iOS.
Reference flavors guide - YouTube Video Only the "dev" and "prod" environments follow the same steps to create a staging environment if needed. Additionally, update the same code in the main folder. Here, too, in the app_config.dart, add a new variable named staging to the AppEnvironment
- **Add/Edit Schema:
-
Configuration: In the image below, all build modes (Debug, Profile, and Release) have been shown for
dev
andprod
flavors (Flavors). -
Fix Main File Path: Go to Runner -> Target -> Build Settings -> All, search for
flutter_target
, specify value for each main file. -
Fix Display App Name: Go to Runner -> Target -> Build Settings -> All, search for
product name
, specify value for each main file. -
Fix Bundle Identifier: Go to Runner -> Signing & Capability -> Build Settings -> All.
Using "dev" and "prod" flavors as examples, with "Debug" and "Release" modes:
For detailed information on how Riverpod is used in this project, please refer to the Features section, where each feature is accompanied by a guide based on Riverpod's functionality and requirements.
- follow official documentation Auto Route
- must run Generating Code to generate route
This Flutter project utilizes the following packages:
- Riverpod - State management
- Retrofit - API call
- Dio - HTTP client
- Build Runner - Code generation
- Freezed - Code generation for models
- Freezed Annotations - Annotations for code generation
Start widget inspection. See below picture
Select mode -> Click button 'Toggle select widget mode'. See below picture
Select widgets - Explore the widget tree to view details. Refer to the following information about the tree:. See below picture
End widget inspection. See below picture
Guide CI/CD For detailed guidance refer to: GitHub Actions Quickstart
Github Actions (CI/CD)
-
Visit flutter-actions/setup-flutter and copy the provided basic version. Paste it into any file, give it a name, commit, push, and create a pull request. This action will automatically begin running.
-
If you have an extra step, add it to the .yml file:
- name: Generate code run: dart run build_runner build --delete-conflicting-outputs
-
Now, check if the automatic CI running has passed.
- Simply copy and paste the
build.yml
file into your repository under.github/workflows/build.yml
, ensuring to specify the correct version of the Flutter SDK, and it will automatically start building.
- Main library
flutter_riverpod: ^2.2.0
riverpod_annotation: ^2.3.3
- Use flutter hooks to manage state of variables
flutter_hooks
- StatefulHookConsumerWidget which offers HookWidget and ConsumerStatefulWidget both features.
hooks_riverpod
For example API usage, refer to the list below: For example API usage, refer to the API List.
-
Can use the
hooks_riverpod
package, StatefulHookConsumerWidget which offers HookWidget and ConsumerStatefulWidget both features. -
All pages must be suffixed by 'Page' to generate auto router automatically Example Correct - HomePage // at the end must add Page Wrong - HomeView, HomeWidget, HomeStatefullWidget
-
To resolve compile errors, follow these steps:
- Ensure that generator dependencies are added to
pubspec.yaml
(retrofit_generator, riverpod_generator). - Manually delete generated files (
.g
and.freezed.dart
) before running the build runner commands. - Fix compile issues (except for generated syntax) before running build runner commands again.
- Use hooks for storing widget local state
- Fix command line run ->
flutter run --flavor development
- Target file "lib/main.dart" not found.
- Run using Android Studio configurations
- Able to run using Android Studio
- Implement GitHub APIs using different Riverpod library usages