Skip to content

Instantly share code, notes, and snippets.

@boformer
Last active September 3, 2023 19:40
Show Gist options
  • Save boformer/29d488534ff312a7cc0238b16f1cd0cc to your computer and use it in GitHub Desktop.
Save boformer/29d488534ff312a7cc0238b16f1cd0cc to your computer and use it in GitHub Desktop.
Flutter Service Architecture
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(),
);
}
}
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);
}
}
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(),
);
}
}
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
}
}
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
@fredgrott
Copy link

Best part of this if one abstracts this out it works for all archs mvc, mvvm, etc. and all the state solutions. Good work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment