Created
June 17, 2023 15:58
-
-
Save PlugFox/e58a773a84e815f817ddd75f95c7b263 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/// Dependencies | |
abstract interface class Dependencies { | |
/// The state from the closest instance of this class. | |
factory Dependencies.of(BuildContext context) => InheritedDependencies.of(context); | |
/// App metadata | |
abstract final AppMetadata appMetadata; | |
/// Database | |
abstract final Database database; | |
/// Authentication controller | |
abstract final AuthenticationController authenticationController; | |
/// Settings controller | |
abstract final SettingsController settingsController; | |
/// Cloud repository | |
abstract final IMyRepository myRepository; | |
} | |
final class $MutableDependencies implements Dependencies { | |
$MutableDependencies() : context = <String, Object?>{}; | |
/// Initialization context | |
final Map<Object?, Object?> context; | |
@override | |
late AppMetadata appMetadata; | |
@override | |
late Database database; | |
@override | |
late AuthenticationController authenticationController; | |
@override | |
late SettingsController settingsController; | |
@override | |
late IMyRepository myRepository; | |
Dependencies freeze() => _$ImmutableDependencies( | |
appMetadata: appMetadata, | |
database: database, | |
authenticationController: authenticationController, | |
settingsController: settingsController, | |
myRepository: myRepository, | |
); | |
} | |
final class _$ImmutableDependencies implements Dependencies { | |
_$ImmutableDependencies({ | |
required this.appMetadata, | |
required this.database, | |
required this.authenticationController, | |
required this.settingsController, | |
required this.myRepository, | |
}); | |
@override | |
final AppMetadata appMetadata; | |
@override | |
final Database database; | |
@override | |
final AuthenticationController authenticationController; | |
@override | |
final SettingsController settingsController; | |
@override | |
final IMyRepository myRepository; | |
} |
This file contains hidden or 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/material.dart'; | |
import 'package:project/src/common/model/dependencies.dart'; | |
/// {@template inherited_dependencies} | |
/// InheritedDependencies widget. | |
/// {@endtemplate} | |
class InheritedDependencies extends InheritedWidget { | |
/// {@macro inherited_dependencies} | |
const InheritedDependencies({ | |
required this.dependencies, | |
required super.child, | |
super.key, | |
}); | |
final Dependencies dependencies; | |
/// The state from the closest instance of this class | |
/// that encloses the given context, if any. | |
/// e.g. `InheritedDependencies.maybeOf(context)`. | |
static Dependencies? maybeOf(BuildContext context) => | |
(context.getElementForInheritedWidgetOfExactType<InheritedDependencies>()?.widget as InheritedDependencies?) | |
?.dependencies; | |
static Never _notFoundInheritedWidgetOfExactType() => throw ArgumentError( | |
'Out of scope, not found inherited widget ' | |
'a InheritedDependencies of the exact type', | |
'out_of_scope', | |
); | |
/// The state from the closest instance of this class | |
/// that encloses the given context. | |
/// e.g. `InheritedDependencies.of(context)` | |
static Dependencies of(BuildContext context) => maybeOf(context) ?? _notFoundInheritedWidgetOfExactType(); | |
@override | |
bool updateShouldNotify(InheritedDependencies oldWidget) => false; | |
} |
This file contains hidden or 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 'dart:async'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'package:project/src/common/model/dependencies.dart'; | |
import 'package:project/src/feature/initialization/data/initialize_dependencies.dart'; | |
/// Ephemerally initializes the app and prepares it for use. | |
Future<Dependencies>? _$initializeApp; | |
/// Initializes the app and prepares it for use. | |
Future<Dependencies> $initializeApp({ | |
void Function(int progress, String message)? onProgress, | |
FutureOr<void> Function(Dependencies dependencies)? onSuccess, | |
void Function(Object error, StackTrace stackTrace)? onError, | |
}) => | |
_$initializeApp ??= Future<Dependencies>(() async { | |
late final WidgetsBinding binding; | |
final stopwatch = Stopwatch()..start(); | |
try { | |
binding = WidgetsFlutterBinding.ensureInitialized()..deferFirstFrame(); | |
/* await SystemChrome.setPreferredOrientations([ | |
DeviceOrientation.portraitUp, | |
DeviceOrientation.portraitDown, | |
]); */ | |
await _catchExceptions(); | |
final dependencies = await $initializeDependencies(onProgress: onProgress).timeout(const Duration(minutes: 5)); | |
await onSuccess?.call(dependencies); | |
return dependencies; | |
} on Object catch (error, stackTrace) { | |
onError?.call(error, stackTrace); | |
ErrorUtil.logError(error, stackTrace, hint: 'Failed to initialize app').ignore(); | |
rethrow; | |
} finally { | |
stopwatch.stop(); | |
binding.addPostFrameCallback((_) { | |
// Closes splash screen, and show the app layout. | |
binding.allowFirstFrame(); | |
//final context = binding.renderViewElement; | |
}); | |
_$initializeApp = null; | |
} | |
}); | |
/// Resets the app's state to its initial state. | |
@visibleForTesting | |
Future<void> $resetApp(Dependencies dependencies) async {} | |
/// Disposes the app and releases all resources. | |
@visibleForTesting | |
Future<void> $disposeApp(Dependencies dependencies) async {} | |
Future<void> _catchExceptions() async { | |
try { | |
PlatformDispatcher.instance.onError = (error, stackTrace) { | |
ErrorUtil.logError( | |
error, | |
stackTrace, | |
hint: 'ROOT ERROR\r\n${Error.safeToString(error)}', | |
).ignore(); | |
return true; | |
}; | |
final sourceFlutterError = FlutterError.onError; | |
FlutterError.onError = (final details) { | |
ErrorUtil.logError( | |
details.exception, | |
details.stack ?? StackTrace.current, | |
hint: 'FLUTTER ERROR\r\n$details', | |
).ignore(); | |
// FlutterError.presentError(details); | |
sourceFlutterError?.call(details); | |
}; | |
} on Object catch (error, stackTrace) { | |
ErrorUtil.logError(error, stackTrace).ignore(); | |
} | |
} |
This file contains hidden or 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
/// Initializes the app and returns a [Dependencies] object | |
Future<Dependencies> $initializeDependencies({ | |
void Function(int progress, String message)? onProgress, | |
}) async { | |
final dependencies = $MutableDependencies(); | |
final totalSteps = _initializationSteps.length; | |
var currentStep = 0; | |
for (final step in _initializationSteps.entries) { | |
currentStep++; | |
final percent = (currentStep * 100 ~/ totalSteps).clamp(0, 100); | |
onProgress?.call(percent, step.key); | |
l.v6('Initialization | $currentStep/$totalSteps ($percent%) | "${step.key}"'); | |
await step.value(dependencies); | |
} | |
return dependencies.freeze(); | |
} | |
typedef _InitializationStep = FutureOr<void> Function($MutableDependencies dependencies); | |
final Map<String, _InitializationStep> _initializationSteps = <String, _InitializationStep>{ | |
'Platform pre-initialization': (_) => $platformInitialization(), | |
'Creating app metadata': (dependencies) => dependencies.appMetadata = AppMetadata( | |
isWeb: platform.isWeb, | |
isRelease: platform.buildMode.isRelease, | |
appName: pubspec.name, | |
appVersion: pubspec.version, | |
appVersionMajor: pubspec.major, | |
appVersionMinor: pubspec.minor, | |
appVersionPatch: pubspec.patch, | |
appBuildTimestamp: pubspec.build.isNotEmpty ? (int.tryParse(pubspec.build.firstOrNull ?? '-1') ?? -1) : -1, | |
operatingSystem: platform.operatingSystem.name, | |
processorsCount: platform.numberOfProcessors, | |
appLaunchedTimestamp: DateTime.now(), | |
locale: platform.locale, | |
deviceVersion: platform.version, | |
deviceScreenSize: ScreenUtil.screenSize().representation, | |
), | |
'Observer state managment': (_) => Controller.observer = ControllerObserver(), | |
'Initializing analytics': (_) async {/* ... */}, | |
'Log app open': (_) {}, | |
'Get remote config': (_) async {/* ... */}, | |
'Preparing secure storage': (dependencies) => | |
dependencies.context['SECURE_STORAGE'] = const fss.FlutterSecureStorage(), | |
'Initializing the database': (dependencies) => dependencies.database = Database.lazy(), | |
'Shrink database': (dependencies) async { | |
if (!Config.environment.isProduction) { | |
await dependencies.database.transaction(() async { | |
final log = await (dependencies.database.select<LogTbl, LogTblData>(dependencies.database.logTbl) | |
..orderBy([(tbl) => OrderingTerm(expression: tbl.id, mode: OrderingMode.desc)]) | |
..limit(1, offset: 1000)) | |
.getSingleOrNull(); | |
if (log != null) { | |
await (dependencies.database.delete(dependencies.database.logTbl) | |
..where((tbl) => tbl.time.isSmallerOrEqualValue(log.time))) | |
.go(); | |
} | |
}); | |
} | |
if (DateTime.now().second % 10 == 0) await dependencies.database.customStatement('VACUUM;'); | |
}, | |
'Refresh key value storage': (dependencies) => dependencies.database.refresh(), | |
'Restore settings': (dependencies) async {/* ... */}, | |
'Migrate app from previous version': (dependencies) => AppMigrator.migrate(dependencies.database), | |
'Collect logs': (dependencies) async { | |
if (Config.environment.isProduction) return; | |
await (dependencies.database.select<LogTbl, LogTblData>(dependencies.database.logTbl) | |
..orderBy([(tbl) => OrderingTerm(expression: tbl.time, mode: OrderingMode.desc)]) | |
..limit(LogBuffer.bufferLimit)) | |
.get() | |
.then<List<LogMessage>>((logs) => logs | |
.map((l) => l.stack != null | |
? LogMessageWithStackTrace( | |
date: DateTime.fromMillisecondsSinceEpoch(l.time * 1000), | |
level: LogLevel.fromValue(l.level), | |
message: l.message, | |
stackTrace: StackTrace.fromString(l.stack!)) | |
: LogMessage( | |
date: DateTime.fromMillisecondsSinceEpoch(l.time * 1000), | |
level: LogLevel.fromValue(l.level), | |
message: l.message, | |
)) | |
.toList()) | |
.then<void>(LogBuffer.instance.addAll); | |
l.bufferTime(const Duration(seconds: 1)).where((logs) => logs.isNotEmpty).listen(LogBuffer.instance.addAll); | |
l | |
.map<LogTblCompanion>((log) => LogTblCompanion.insert( | |
level: log.level.level, | |
message: log.message.toString(), | |
time: Value<int>(log.date.millisecondsSinceEpoch ~/ 1000), | |
stack: Value<String?>(switch (log) { LogMessageWithStackTrace l => l.stackTrace.toString(), _ => null }), | |
)) | |
.bufferTime(const Duration(seconds: 15)) | |
.where((logs) => logs.isNotEmpty) | |
.listen( | |
(logs) => | |
dependencies.database.batch((batch) => batch.insertAll(dependencies.database.logTbl, logs)).ignore(), | |
cancelOnError: false, | |
); | |
}, | |
'Log app initialized': (_) {}, | |
}; |
This file contains hidden or 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 'dart:async'; | |
import 'package:flutter/material.dart'; | |
import 'package:l/l.dart'; | |
import 'package:project/src/common/widget/app.dart'; | |
import 'package:project/src/common/widget/app_error.dart'; | |
import 'package:project/src/feature/initialization/data/initialization.dart'; | |
import 'package:project/src/feature/initialization/widget/inherited_dependencies.dart'; | |
import 'package:project/src/feature/initialization/widget/initialization_splash_screen.dart'; | |
/// Entry point of the app. | |
/// Initializes the app and prepares it for use. | |
void main() => l.capture<void>( | |
() => runZonedGuarded<void>( | |
() async { | |
// Splash screen | |
final initializationProgress = ValueNotifier<({int progress, String message})>((progress: 0, message: '')); | |
runApp(InitializationSplashScreen(progress: initializationProgress)); | |
$initializeApp( | |
onProgress: (progress, message) => initializationProgress.value = (progress: progress, message: message), | |
onSuccess: (dependencies) => runApp( | |
InheritedDependencies( | |
dependencies: dependencies, | |
child: App(), | |
), | |
), | |
onError: (error, stackTrace) { | |
runApp(AppError(message: ErrorUtil.formatMessage(error))); | |
ErrorUtil.logError(error, stackTrace).ignore(); | |
}, | |
).ignore(); | |
}, | |
l.e, | |
), | |
const LogOptions( | |
handlePrint: true, | |
messageFormatting: _messageFormatting, | |
outputInRelease: false, | |
printColors: true, | |
), | |
); | |
/// Formats the log message. | |
Object _messageFormatting(Object message, LogLevel logLevel, DateTime now) => '${_timeFormat(now)} | $message'; | |
/// Formats the time. | |
String _timeFormat(DateTime time) => '${time.hour}:${time.minute.toString().padLeft(2, '0')}'; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Очень понравилась идея вызывать на SplashScreen runApp отдельно без инициализации роутера и всего подобного 👍.
Понравилось, но что-то столкнулся с тем что при переходу по url
PlatformDispatcher.defaultRouteName
не подхватывается новым роутером.Пока не нашел в чем проблема, пробовал указывать
restorationScopeId
не помогло.Так же в примере
runApp
вызывается до остановки первого кадра.