Skip to content

Commit

Permalink
refactor app initialization
Browse files Browse the repository at this point in the history
  • Loading branch information
smart7even committed Jan 10, 2024
1 parent 233b804 commit 2b5aacb
Show file tree
Hide file tree
Showing 19 changed files with 431 additions and 113 deletions.
15 changes: 0 additions & 15 deletions lib/common/dependencies/dependencies_scope.dart

This file was deleted.

18 changes: 18 additions & 0 deletions lib/common/model/dependencies.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uneconly/common/database/database.dart';
import 'package:uneconly/feature/initialization/widget/inherited_dependencies.dart';
import 'package:uneconly/feature/settings/data/settings_repository.dart';

class Dependencies {
Dependencies();

late final SharedPreferences sharedPreferences;
late final ISettingsRepository settingsRepository;
late final MyDatabase database;
late final Dio dio;

factory Dependencies.of(BuildContext context) =>
InheritedDependencies.of(context);
}
7 changes: 0 additions & 7 deletions lib/common/routing/navigation_observer.dart

This file was deleted.

58 changes: 58 additions & 0 deletions lib/common/util/error_util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ignore_for_file: avoid_classes_with_only_static_members

import 'dart:async';

import 'package:uneconly/common/util/platform/error_util_vm.dart'
if (dart.library.html) 'package:uneconly/common/util/platform/error_util_js.dart';
import 'package:l/l.dart';

/// Error util.
abstract final class ErrorUtil {
/// Log the error to the console and to Crashlytics.
static Future<void> logError(
Object exception,
StackTrace stackTrace, {
String? hint,
bool fatal = false,
}) async {
try {
if (exception is String) {
return await logMessage(
exception,
stackTrace: stackTrace,
hint: hint,
warning: true,
);
}
$captureException(exception, stackTrace, hint, fatal).ignore();
l.e(exception, stackTrace);
} on Object catch (error, stackTrace) {
l.e(
'Error while logging error "$error" inside ErrorUtil.logError',
stackTrace,
);
}
}

/// Logs a message to the console and to Crashlytics.
static Future<void> logMessage(
String message, {
StackTrace? stackTrace,
String? hint,
bool warning = false,
}) async {
try {
l.e(message, stackTrace ?? StackTrace.current);
$captureMessage(message, stackTrace, hint, warning).ignore();
} on Object catch (error, stackTrace) {
l.e(
'Error while logging error "$error" inside ErrorUtil.logMessage',
stackTrace,
);
}
}

/// Rethrows the error with the stack trace.
static Never throwWithStackTrace(Object error, StackTrace stackTrace) =>
Error.throwWithStackTrace(error, stackTrace);
}
15 changes: 15 additions & 0 deletions lib/common/util/platform/error_util_js.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Future<void> $captureException(
Object exception,
StackTrace stackTrace,
String? hint,
bool fatal,
) =>
Future<void>.value(null);

Future<void> $captureMessage(
String message,
StackTrace? stackTrace,
String? hint,
bool warning,
) =>
Future<void>.value(null);
15 changes: 15 additions & 0 deletions lib/common/util/platform/error_util_vm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Future<void> $captureException(
Object exception,
StackTrace stackTrace,
String? hint,
bool fatal,
) =>
Future<void>.value();

Future<void> $captureMessage(
String message,
StackTrace? stackTrace,
String? hint,
bool warning,
) =>
Future<void>.value();
44 changes: 44 additions & 0 deletions lib/common/widget/app_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';

/// {@template app_error}
/// AppError widget
/// {@endtemplate}
class AppError extends StatelessWidget {
/// {@macro app_error}
const AppError({
this.error,
Key? key,
}) : super(key: key);

/// Error
final Object? error;

@override
Widget build(BuildContext context) => MaterialApp(
title: 'App Error',
theme: View.of(context).platformDispatcher.platformBrightness ==
Brightness.dark
? ThemeData.dark(useMaterial3: true)
: ThemeData.light(useMaterial3: true),
home: Scaffold(
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
// ErrorUtil.formatMessage(error)
error?.toString() ?? 'Something went wrong',
textScaler: TextScaler.noScaling,
),
),
),
),
),
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.noScaling,
),
child: child!,
),
);
}
79 changes: 79 additions & 0 deletions lib/feature/initialization/data/initialization.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:async';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:uneconly/common/model/dependencies.dart';
import 'package:uneconly/common/util/error_util.dart';
import 'package:uneconly/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 _catchExceptions();
final dependencies =
await $initializeDependencies(onProgress: onProgress)
.timeout(const Duration(minutes: 7));
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();
}
}
63 changes: 63 additions & 0 deletions lib/feature/initialization/data/initialize_dependencies.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'dart:async';

import 'package:dio/dio.dart';
import 'package:l/l.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uneconly/common/database/database.dart';
import 'package:uneconly/common/model/dependencies.dart';
import 'package:uneconly/constants.dart';
import 'package:uneconly/feature/initialization/data/platform/platform_initialization.dart';
import 'package:uneconly/feature/settings/data/settings_local_data_provider.dart';
import 'package:uneconly/feature/settings/data/settings_repository.dart';

/// Initializes the app and returns a [Dependencies] object
Future<Dependencies> $initializeDependencies({
void Function(int progress, String message)? onProgress,
}) async {
final dependencies = Dependencies();
final totalSteps = _initializationSteps.length;
var currentStep = 0;
for (final step in _initializationSteps.entries) {
try {
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);
} on Object catch (error, stackTrace) {
l.e('Initialization failed at step "${step.key}": $error', stackTrace);
Error.throwWithStackTrace(
'Initialization failed at step "${step.key}": $error',
stackTrace,
);
}
}
return dependencies;
}

typedef _InitializationStep = FutureOr<void> Function(
Dependencies dependencies,
);
final Map<String, _InitializationStep> _initializationSteps =
<String, _InitializationStep>{
'Platform pre-initialization': (_) => $platformInitialization(),
'Log app open': (_) {},
'Initialize shared preferences': (dependencies) async =>
dependencies.sharedPreferences = await SharedPreferences.getInstance(),
'Initialize settings repository': (dependencies) async =>
dependencies.settingsRepository = SettingsRepository(
localDataProvider: SettingsLocalDataProvider(
prefs: dependencies.sharedPreferences,
),
),
'Initialize database': (dependencies) async =>
dependencies.database = MyDatabase(),
'Initialize dio': (dependencies) async => dependencies.dio = Dio(
BaseOptions(
baseUrl: serverAddress,
),
),
'Log app initialized': (_) {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'platform_initialization_vm.dart'
if (dart.library.html) 'platform_initialization_js.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'dart:io' as io;

Future<void> $platformInitialization() =>
io.Platform.isAndroid || io.Platform.isIOS
? _mobileInitialization()
: _desktopInitialization();

Future<void> _mobileInitialization() async {}

Future<void> _desktopInitialization() async {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Future<void> $platformInitialization() async {
//setUrlStrategy(const HashUrlStrategy());
}
29 changes: 29 additions & 0 deletions lib/feature/initialization/widget/inherited_dependencies.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:uneconly/common/model/dependencies.dart';

class InheritedDependencies extends InheritedWidget {
final Dependencies dependencies;

const InheritedDependencies({
required this.dependencies,
required super.child,
super.key,
});

static Dependencies of(BuildContext context) =>
maybeOf(context) ?? _notFoundInheritedWidgetOfExactType();

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',
);

@override
bool updateShouldNotify(InheritedDependencies oldWidget) => false;
}
5 changes: 2 additions & 3 deletions lib/feature/loading/widget/loading_page.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:home_widget/home_widget.dart';
import 'package:octopus/octopus.dart';
import 'package:uneconly/common/dependencies/dependencies_scope.dart';
import 'package:uneconly/common/model/dependencies.dart';
import 'package:uneconly/common/routing/routes.dart';
import 'package:uneconly/feature/settings/data/settings_repository.dart';

Expand Down Expand Up @@ -48,7 +47,7 @@ class _LoadingPageState extends State<LoadingPage> {

Future<void> onOpen() async {
ISettingsRepository settingsRepository =
context.read<DependenciesScope>().settingsRepository;
Dependencies.of(context).settingsRepository;

final group = await settingsRepository.getGroup();

Expand Down
Loading

0 comments on commit 2b5aacb

Please sign in to comment.