Skip to content

89hnim/base-flutter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Base flutter [Work in progress 🚧]

Goals

  • Easy for developers to understand, nothing too experimental.
  • Support multiple developers working on the same codebase.
  • Minimize build times.

Architecture overview

Use BLoC design pattern

Bloc is a design pattern created by Google to help separate business logic from the presentation layer and enable a developer to reuse code more efficiently.

To archive this, we use a state management library called Bloc was created and maintained by Felix Angelo.
The app has two layers: Data layer and UI layer (called presentation) architecture-overview

Data flow

The architecture follows a reactive programming model with unidirectional data flow:

  1. User trigger an event (press button on screen .etc.)
  2. The View will send that event to bloc
  3. bloc handle the event and request corresponding data from repository
  4. repository request data from data provider
  5. data provider is the data source, could from cache or remote. After got the data, return to repository
  6. repository could do some logic like sync data and return the requested data to bloc. bloc updates the state.
  7. While view is observing bloc, it will receive the state corresponding to the sent event and update the interface

Models mapping

For best maintainability, I decided to use four types of models in the app:

  1. Cache Entity: model to read & write in cache
  2. Remote DTO: model parsed from network
  3. Domain Model: app model, nearly don't change
  4. Ui Data: display data in ui

To analyze this approach:

Pros:

  • Code is clearer when the model is decoupled by layers
  • Easily maintain the codebase, especially case data from network change too often
  • Ui data helps us to increase performance better 🤫

Cons:

  • Need to write more code

Deep dive into Architecture

So above is the concept of this architecture. But how is it actually implemented in each layer?

(Data Layer) Data Provider

Remote

remote-package

  • models: Dto models
  • exceptions: handle all exceptions from server. Example when server response with a incorrect status, such as 404, 503...
  • apis: call api here, using Dio for making network request
Future<List<SampleDto>> fetchCompute() async {
    final Response response = await _dio.request("/photos");
    return JsonExtensions.toObjectsCompute<SampleDto>(
        response.data.toString(),
        (json) => SampleDto.fromJson(json),
    );
  }

The JsonExtensions is a utility class help convert json to corresponding model in background or not.

Cache

  • models: entity models
  • storage: get cache data here. Currently I'm prefer using SharedPreferences, because web platform doesn't support sqlite.
/// save data in json format
  Future<bool> saveSamples(List<SampleEntity> samples) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return await prefs.setString(_samplesKey, jsonEncode(samples));
  }
  
  /// get data from cache
  Future<List<SampleEntity>> getSamples() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return JsonExtensions.toObjects(
        prefs.getString(_samplesKey).orEmpty(),
        (json) => SampleEntity.fromJson(json),
    );
  }

(Data Layer) Repository

Get the data from cache or remote then map to domain model

Future<Result<List<SampleModel>>> fetch() async {
    return Result.guardFuture(
      () async => (await _sampleApiClient.fetchCompute()).mapToModel(),
      _exceptionHandler,
    );
  }

The Result.guardFuture() simply wraps Future with try catch. Also we have Result.guard() does the same thing but not Future. See more in result.dart class
The mapToModel is an extension, put in the Dto model

extension _Mapper on SampleDto {
  SampleModel? mapToModel() {
    if (id == null || albumId == null) return null;
    return SampleModel(
      id: id!,
      albumId: albumId!,
      title: title.orEmpty(),
      url: url.orEmpty(),
      thumbnailUrl: thumbnailUrl.orEmpty(),
    );
  }
}

extension ListMapper on List<SampleDto> {
  List<SampleModel> mapToModel() {
    return map((e) => e.mapToModel()).whereType<SampleModel>().toList();
  }
}

Presentation Layer

This layer doesn't have much to say. You can deploy as you like, but for consistency try to follow the following package organization: view-package

  • feature_name/
    • logic/ cubits, blocks, event
    • models/ ui data
    • view/ your screen/page (for consistency use page for naming view)
    • widgets/ widgets use for page

Navigation

Use onGenerateRoute for setting up routes. All available routes will be declared in AppRouter.
Add new route:

/// create const route's name
 static const String sample = 'sample';
 /// declare in onGenerateRoute
 static Route onGenerateRoute(RouteSettings settings) {
 ...
    case sample:
      return MaterialPageRoute(
        builder: (_) => const SamplePage(),
      );
  ...
  }

Navigate:

Navigator.of(context).pushNamed(AppRouter.sample);

Design system [Work in progress 🚧]

DS organization in core/ui folder.

Naming convention

  • Follow offical guidance
  • package try not use "_". Example home_tracking can be replaced with tracking/home
  • Util extension end with _ext. Example json_ext
  • bloc event end with Event. Example SampleRequestDataEvent, SampleClearRequestedDataEvent
  • bloc state end with State. Example SampleState, SampleLoadingState
  • models:
    • domain model: end with Model (SampleModel)
    • remote model: end with Dto (SampleDto)
    • cache model: end with Entity (SampleEntity)
    • ui model: end with UiData (SampleUiData)

Build

get dependencies

flutter packages get

run build runner for generating needed classes

 flutter pub run build_runner build --delete-conflicting-outputs  

Todo

  • Base data flow using BLoC pattern
  • Basic usages of bloc to manage app state
  • Handle exceptions from API
  • [Mobile] Json decode in background
  • [Web] Json decode in background
  • Network interceptor
  • Design system: snackbar
  • Design system: typography
  • Design system: colors, theme
  • Add useful extensions for String based on kotlin
  • Add useful extensions for List based on kotlin
  • Refactoring a lotttt when I have more experience with Flutter 🥺

About

Base Flutter Architecture using BLoC pattern

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published