Skip to content

justforworkandstuff/riverpod_flutter_base

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Riverpod Flutter Base Template

License: MIT

This is a base project that is developed with Riverpod being the heart of the project in mind.

Basic features incorporated in this project includes:

  • Flavouring
  • Firebase Integration
  • Notification handler
  • Sample API calls
  • Localizations
  • Shared Preference as local storage
  • Go Router
  • Base pages and util method to quickly start off the development

Table of contents

Getting Started πŸš€

  1. This project contains 3 flavors:
  • development
  • staging
  • production
  1. To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands in terminal:
# Development
$ flutter run --flavor development --target lib/main_development.dart

# Staging
$ flutter run --flavor staging --target lib/main_staging.dart

# Production
$ flutter run --flavor production --target lib/main_production.dart
  1. To make configurations/options based on flavors, add/update configs in app_options.dart class at lib/app/core/ directory
class DevelopmentConstant {
  static const String apiEndpoint = 'Your api url';
}

class StagingConstant {
  static const String apiEndpoint = 'Your api url';
}

class ProductionConstant {
  static const String apiEndpoint = 'Your api url';
}

Folder structure

The directory of the project should strictly follow these patterns in order to keep the project clean and simple:

   |-- lib
   |   |-- app
   |   |   |-- features
   |   |   |   |-- newFeature
   |   |   |   |   |-- widgets
   |   |   |   |   |-- model
   |   |   |   |   |-- notifiers
   |   |   |   |   |-- providers
   |   |   |   |   |-- repo
   |   |   |   |   |-- service
   |   |   |   |   |   |-- view.dart
   |   |-- generated
   |   |-- l10n

Project Architecture

The architecture of the project adapts the following flow:

View > Controllers (Notifiers) > Service > Repo > Data source (with Dio)

The reason why this architecture is adapted it due to Riverpod being the core of the project.

View

  • Should be as dumb as possible to have separation of concern
  • Should avoid usage of setState as much as possible and use Riverpod for the UI state management
  • Should have at least one or more controllers for each view to handle each state of the UI components
  • Should extend base_consumer_stateful_widget.dart or base_consumer_widget.dart page for basic unified features and widgets

Controller

  • Should be used with Riverpod's Notifier Provider
  • Should be used to handle the state changes of the presentation layer
  • Should be reused where possible

Services

  • Should be used to separate the business logic and data source from each other
  • Should be used to de-clutter the business logic from controller
  • Should perform an action (e.g. get data from local/network, combine data from different sources before passing it back to controller)

Repository (Data source)

  • Should be use as an intermediary between the business logic and data source
  • Should be used to communicate with the data sources (e.g. remote or local sources)
  • Should be used as a data storage

State Management

This project relies on Riverpod as the state management tool, along with the usage of code-generation through build_runner.

To start off, please have a look at Riverpod's Quickstart guide.

Core concepts in Riverpod:

  1. Provider
  2. Notifier

Using the Provider

  1. The usage of provider is to represent a value (that may be changed in the future) to be used in multiple places.
  2. Example of the usage of provider may be seen through lib/app/core/configurations/app_options_providers.dart.
  3. For any new value that are related to app options, please register in the lib/app/core/configurations/app_options_providers.dart.
@riverpod
String stringValue(StringValueRef ref) {
  return 'string value';
}
  1. To access a provider value:
String getValue(WidgetRef ref) {
  return ref.read(stringValueProvider);
}

Using the Notifier

  1. The usage of notifier is to act as a controller to manage the state of the UI.
  2. Examples of the usage of a notifier can be referenced from examples of the features in lib/app/features/.
  3. An simple example of notifier usage are as following: Note: This is used along with code-generator.
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'file_name.g.dart';

@riverpod
class CounterValue extends _$CounterValue {
  @override
  int build() {
    return 0;
  }

  void increment() => state++;

  void decrement() => state--;
}
  1. Usage of the notifier can be done so by:
@override
Widget body(BuildContext context, WidgetRef ref) {
  final counterValue = ref.watch(counterValueProvider);

  return Text(counterValue.toString());
}

@override
Widget floatingActionButton(WidgetRef ref, BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.end,
    children: [
      FloatingActionButton(
          onPressed: () {
            ref.read(counterControllerProvider.notifier).increment();
          },
          child: const Icon(Icons.add)),
      const SizedBox(width: 10),
      FloatingActionButton(
          onPressed: () {
            ref.read(counterControllerProvider.notifier).decrement();
          },
          child: const Icon(Icons.remove)),
    ],
  );
}
  1. Notice that for notifiers we would need to explicitly specify customProvider.notifer in order to access to the notifier's methods.

App Routing with Go Router

This project using router to navigating between screens and handling deep links. go_router package is used due to its capability to support deeplink handling, Flutter's Navigation 2.0 (declarative API routing mechanism) as well as the package being officially endorsed and maintained by Flutter team.

Declaring Routes

lib/app/core/app_router.dart is the class used for the configurations of all routes within the project. For any new screens or new routes, you may add in a GoRoute object into the GoRouter constructor.

GoRoute

To configure a GoRoute, a path template and builder must be provided. Specifiy a path template to handle by providing a path parameter, and a builder by providing either the builder or a pageBuilder parameter:

final GoRouter router = GoRouter(routes: [
  GoRoute(path: '/login', builder: (context, state) => LoginPage())
]);

Child Routes

A matched route can result in more than one screen being displayed on a Navigator. This is equivalent to calling `push()', where a new screen is displayed above the previous screen with a transition animation.

To display a screen on top of another, add a child route by adding it to the parent route's `routes' list:

final GoRouter router = GoRouter(routes: [
  GoRoute(path: '/login', builder: (context, state) => LoginPage()),
  GoRoute(path: 'profile', builder: (context, state) => HomePage(initialIndex: 4), routes: [
    GoRoute(
        path: 'editProfile',
        builder: (context, state) => EditBasicInfoPage(),
        routes: [
          GoRoute(path: 'changePhoneNumber', builder: (context, state) => ChangePhoneNumberPage())
        ]),
    GoRoute(path: 'changeLanguage', builder: (context, state) => LanguageListPage())
  ])
]);

Navigation and Redirection

Go directly to a destination

Navigating to a destination in GoRouter will replace the current stack of screens with the screens configured to be displayed for the destination route. To change to a new screen, call context.go() with a URL, as the following example:

context.go('/login');

Imperative navigation

GoRouter can push a screen onto the Navigator's history stack using context.push(), and can pop the current screen via context.pop(). However, imperative navigation is known to cause issues with the browser history, thus it is advised to avoid using it as much as possible.

Returning values from pages with Go Router

You can wait for a value to be returned from the destination:

Initial page:

await context.go('/login');
if (result...) ...

Returning page:

final result = true;
context.pop(result);

Working with Firebase

This project integrated Firebase product such as Firebase Cloud Messaging, Analytics, as well as Crashlytic for app analytic and performance monitoring. The integration of Firebase components are following the Add Firebase to your Flutter app.

Firebase Configuration

  1. To update configuration key and identifiers, look for the configuration class in lib/app/core/configurations/firebase_options.dart and update the respective configuration accordingly.

Example:

static const FirebaseOptions android = FirebaseOptions(
    apiKey: 'Your api key',
    appId: 'Your app id',
    messagingSenderId: 'Your messaging sender id',
    projectId: 'Your project id',
    storageBucket: 'Your storage bucket id',
);

static const FirebaseOptions ios = FirebaseOptions(
    apiKey: 'Your api key',
    appId: 'Your app id',
    messagingSenderId: 'Your sender id',
    projectId: 'Your project id',
    storageBucket: 'Your storage bucket',
    iosClientId: 'Your iosClientId',
    iosBundleId: 'Your iosBundleId',
);

Notifications

A basic notification handling custom class is included within the project (lib/app/core/handlers/notification_handler.dart), which is tasked to handle messages received from notifications, in each state:

  1. Foreground
  2. Background
  3. Terminated

All of these respective states have their own logic and criteria for display, for more info, may refer to Firebase Cloud Messaging official documentation.

In this project, we're applying the notification handling with two main packages: firebase_messaging and flutter_local_notifications.

The reason for this, is that Android have a custom behaviour that does not show notifications, if user is in foreground state. Therefore, we need to use flutter_local_notifications to show in foreground state.

Working with Translations 🌐

This project relies on flutter_localizations and follows the official internationalization guide for Flutter.

Note: This project uses Flutter's Intl Plugin to work with localizations, when enabled plugin should automatically detect any changes in the intl_language.arb files and update accordingly. Pre-requisites: Download plugin from Flutter_Intl_Plugin.

Adding Strings

  1. To add a new localizable string, open the intl_en.arb file at lib/l10n/intl_en.arb.
{
    "helloWorld": "Hello World",
    "@helloWorld": {
        "description": "Hello World Text"
    }
}
  1. To add a key/value pair:
{
    "helloWorld": "Hello World",
    "@helloWorld": {
        "description": "Hello World Text"
    },
    "nameIs": "Name is: {value}",
    "@nameIs": {
        "placeholders": {
            "value": {
                "type": "String"
            }
        }
    }
}
  1. Use the strings as following example
import 'package:dumbdumb_flutter_app/l10n/l10n.dart';

@override
Widget build(BuildContext context) {
  return Row(
      children: [
        Text(S.current.helloWorld),
        Text(S.current.nameIs('Testing'))
      ]
  );
}

Adding Supported Locales

Update the CFBundleLocalizations array in the Info.plist at ios/Runner/Info.plist to include the new locale.

    ...

<key>CFBundleLocalizations</key>    <array>
<string>en</string>
<string>es</string>
</array>

    ...

Adding Translations

  1. For each supported locale, add a new ARB file in lib/l10n/.
β”œβ”€β”€ l10n
β”‚   β”œβ”€β”€ arb
β”‚   β”‚   β”œβ”€β”€ intl_en.arb
β”‚   β”‚   └── intl_es.arb
  1. Add the translated strings to each .arb file:

intl_en.arb

{
    "helloWorld": "Hello World",
    "@helloWorld": {
        "description": "Text used in the first page"
    }
}

intl_es.arb

{
    "helloWorld": "Hola Mundo",
    "@helloWorld": {
        "description": "Texto utilizado en la primera pΓ‘gina."
    }
}

Credits

A grateful and honest appreciation to the very_good_cli team for their setup of flavouring, localizations and many more features for a Flutter base template to quickly kick start on a Flutter project.

Besides, here's another toast of utmost gratitude to heickjack619lok, for starting off a base project dumbdumb_flutter which this template project origins from. In the initial project, it has already covered up a lot of the features and principles that are ready to be used and thus allowing the speedy preparation of this template to be ready of use.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages