Last active
September 3, 2023 19:40
-
-
Save boformer/29d488534ff312a7cc0238b16f1cd0cc to your computer and use it in GitHub Desktop.
Flutter Service Architecture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:architecture_playground/home_page.dart'; | |
import 'package:architecture_playground/services.dart'; | |
import 'package:flutter/material.dart'; | |
void main() async { | |
// perform long-running tasks before "runApp" to display the native splash screen | |
final services = await Services.initialize(); | |
runApp(ServicesProvider( | |
services: services, | |
child: App(), | |
)); | |
} | |
class App extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Architecture Demo', | |
home: HomePage(), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/widgets.dart'; | |
import 'package:rxdart/rxdart.dart'; | |
import 'package:shared_preferences/shared_preferences.dart'; | |
/// Service container | |
class Services { | |
final SharedPreferences sharedPrefs; | |
final DataService dataService; | |
final AuthService authService; | |
Services(this.sharedPrefs, this.dataService, this.authService); | |
static Future<Services> initialize() async { | |
final sharedPrefs = await SharedPreferences.getInstance(); | |
final dataService = DataService(); | |
await dataService.initialize(); | |
// service that depends on other services | |
final authService = AuthService(sharedPrefs, dataService); | |
return Services(sharedPrefs, dataService, authService); | |
} | |
static Services of(BuildContext context) { | |
// A bit different from a normal inherited widget. Widgets can call this from initState, | |
// and it is assumed that the services never change during the lifetime of the app | |
final provider = context.ancestorInheritedElementForWidgetOfExactType(ServicesProvider).widget as ServicesProvider; | |
return provider.services; | |
} | |
} | |
class ServicesProvider extends InheritedWidget { | |
final Services services; | |
ServicesProvider({Key key, this.services, Widget child}) : super(key: key, child: child); | |
@override | |
bool updateShouldNotify(ServicesProvider old) { | |
if (services != old.services) { | |
throw Exception('Services must be constant!'); | |
} | |
return false; | |
} | |
} | |
// Example services: | |
class AuthService { | |
final SharedPreferences _sharedPrefs; | |
final DataService _dataService; | |
AuthService(this._sharedPrefs, this._dataService); | |
// getters, fields, methods, obervables... | |
} | |
class DataService { | |
Observable<int> get magicNumber$ => _magicNumber$; | |
final _magicNumber$ = BehaviorSubject<int>(); | |
DataService() {} | |
Future<void> initialize() async { | |
// open database, contact server... | |
await Future.delayed(Duration(seconds: 2)); | |
_magicNumber$.add(42); | |
} | |
void increaseMagic() { | |
_magicNumber$.add(_magicNumber$.value + 1); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:architecture_playground/home_bloc.dart'; | |
import 'package:architecture_playground/services.dart'; | |
import 'package:flutter/material.dart'; | |
class HomePage extends StatefulWidget { | |
@override | |
_HomePageState createState() => _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> { | |
HomeBloc _bloc; | |
@override | |
void initState() { | |
super.initState(); | |
// "HomeBloc" is a scoped service. There's no DI, so we have to initialize it manually. | |
// if required, you can create another provider InheritedWidget to make it available to child widgets | |
final services = Services.of(context); | |
_bloc = HomeBloc(services.dataService); | |
} | |
@override | |
void dispose() { | |
_bloc.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(title: Text('Architecture Demo')), | |
body: StreamBuilder<String>( | |
stream: _bloc.magicString$, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return Center( | |
child: Text(snapshot.data), | |
); | |
} else { | |
return Loading(); | |
} | |
}, | |
), | |
); | |
} | |
} | |
class Loading extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Center( | |
child: CircularProgressIndicator(), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:architecture_playground/services.dart'; | |
import 'package:rxdart/rxdart.dart'; | |
class HomeBloc { | |
final DataService _dataService; | |
Observable<String> get magicString$ => _magicString$; | |
Observable<String> _magicString$; | |
HomeBloc(this._dataService) { | |
_magicString$ = _dataService.magicNumber$.map((n) => 'The magic number is $n'); | |
} | |
void dispose() { | |
// do stuff when UI model is disposed | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: architecture_playground | |
description: A new Flutter application. | |
version: 1.0.0+1 | |
environment: | |
sdk: ">=2.0.0-dev.68.0 <3.0.0" | |
dependencies: | |
flutter: | |
sdk: flutter | |
cupertino_icons: ^0.1.2 | |
shared_preferences: ^0.4.3 | |
rxdart: ^0.19.0 | |
dev_dependencies: | |
flutter_test: | |
sdk: flutter | |
flutter: | |
uses-material-design: true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Best part of this if one abstracts this out it works for all archs mvc, mvvm, etc. and all the state solutions. Good work!