feat(app): Вынести инициализацию приложения за splash (#4)

Co-authored-by: PetrovY <y.petrov@friflex.com>
This commit is contained in:
Yuri Petrov
2025-02-12 10:53:38 +03:00
committed by GitHub
parent 0a7452e1eb
commit af3b941711
18 changed files with 503 additions and 155 deletions

View File

@@ -0,0 +1,19 @@
{
"imports": [
"import 'package:flutter/widgets.dart';"
],
"classStructure": [
"/// {@template {className}}",
"/// ",
"/// {@endtemplate}",
"class {className} extends StatelessWidget {",
" /// {@macro {className}}",
" const {className}({super.key});",
" ",
" @override",
" Widget build(BuildContext context) {",
" return const Placeholder();",
" }",
"}"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1,46 +1,96 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_providers.dart';
import 'package:friflex_starter/app/depends_providers.dart';
import 'package:friflex_starter/app/theme/app_theme.dart'; import 'package:friflex_starter/app/theme/app_theme.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart'; import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/error/error_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:friflex_starter/l10n/gen/app_localizations.dart'; import 'package:friflex_starter/l10n/gen/app_localizations.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
/// Класс для реализации объекта приложения /// Класс приложения
class App extends StatelessWidget { class App extends StatefulWidget {
/// Создает экземпляр приложения
///
/// Принимает:
/// - [diContainer] - набор зависимостей приложения
/// - [router] - экземпляр роутера приложения
const App({ const App({
super.key, super.key,
required this.diContainer,
required this.router, required this.router,
required this.initDependencies,
}); });
/// Роутер приложения
/// Набор зависимостей приложения
final DiContainer diContainer;
/// Экземпляр роутера приложения
final GoRouter router; final GoRouter router;
/// Функция для инициализации зависимостей
final Future<DiContainer> Function() initDependencies;
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
/// Мутабельная Future для инициализации зависимостей
late Future<DiContainer> _initFuture;
@override
void initState() {
super.initState();
_initFuture = widget.initDependencies();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppProviders( return AppProviders(
diContainer: diContainer, // Consumer для локализации добавляем выше чем DependsProviders
// чтобы при изменении локализации перестраивался весь виджет
// Но, это не обязательно, можно добавить в DependsProviders
child: LocalizationConsumer( child: LocalizationConsumer(
builder: () => ThemeConsumer( builder: () => FutureBuilder<DiContainer>(
builder: () => _App(router: router), future: _initFuture,
builder: (_, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
// Пока инициализация показываем Splash
return const SplashScreen();
case ConnectionState.done:
if (snapshot.hasError) {
return ErrorScreen(
error: snapshot.error,
stackTrace: snapshot.stackTrace,
onRetry: _retryInit,
);
}
final diContainer = snapshot.data;
if (diContainer == null) {
return ErrorScreen(
error:
'Ошибка инициализации зависимостей, diContainer = null',
stackTrace: null,
onRetry: _retryInit,
);
}
return DependsProviders(
diContainer: diContainer,
child: ThemeConsumer(
builder: () => _App(router: widget.router),
),
);
}
},
), ),
), ),
); );
} }
void _retryInit() {
setState(() {
_initFuture = widget.initDependencies();
});
}
} }
/// Класс для реализации объекта приложения
class _App extends StatelessWidget { class _App extends StatelessWidget {
const _App({required this.router}); const _App({required this.router});
@@ -59,31 +109,3 @@ class _App extends StatelessWidget {
); );
} }
} }
/// Класс для реализации провайдеров приложения
final class AppProviders extends StatelessWidget {
const AppProviders({
super.key,
required this.child,
required this.diContainer,
});
final Widget child;
final DiContainer diContainer;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider.value(value: diContainer), // Передаем контейнер зависимостей
ChangeNotifierProvider(
create: (_) => ThemeNotifier(),
), // Провайдер для темы
ChangeNotifierProvider(
create: (_) => LocalizationNotifier(),
), // Провайдер для локализации
],
child: child,
);
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:provider/provider.dart';
/// Класс для добавления провайдеров темы и локализации
final class AppProviders extends StatelessWidget {
const AppProviders({
super.key,
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => ThemeNotifier(),
), // Провайдер для темы
ChangeNotifierProvider(
create: (_) => LocalizationNotifier(),
), // Провайдер для локализации
],
child: child,
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:provider/provider.dart';
/// Класс для внедрения глобальных зависимостей
final class DependsProviders extends StatelessWidget {
const DependsProviders({
super.key,
required this.child,
required this.diContainer,
});
final Widget child;
final DiContainer diContainer;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Сюда добавляем глобальные блоки, inherited и т.д.
Provider.value(value: diContainer), // Передаем контейнер зависимостей
],
child: child,
);
}
}

View File

@@ -1,13 +1,12 @@
import 'package:app_services/app_services.dart';
import 'package:friflex_starter/app/app_config/app_config.dart'; import 'package:friflex_starter/app/app_config/app_config.dart';
import 'package:friflex_starter/app/app_config/i_app_config.dart'; import 'package:friflex_starter/app/app_config/i_app_config.dart';
import 'package:friflex_starter/app/app_env.dart'; import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/app/http/app_http_client.dart'; import 'package:friflex_starter/app/http/app_http_client.dart';
import 'package:friflex_starter/app/http/i_http_client.dart'; import 'package:friflex_starter/app/http/i_http_client.dart';
import 'package:friflex_starter/di/di_repositories.dart'; import 'package:friflex_starter/di/di_repositories.dart';
import 'package:friflex_starter/di/di_services.dart';
import 'package:friflex_starter/di/di_typedefs.dart'; import 'package:friflex_starter/di/di_typedefs.dart';
import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart';
import 'package:i_app_services/i_app_services.dart';
/// {@template dependencies_container} /// {@template dependencies_container}
/// Контейнер для зависимостей /// Контейнер для зависимостей
@@ -22,82 +21,53 @@ final class DiContainer {
/// Сервис для отладки, получаем из конструктора /// Сервис для отладки, получаем из конструктора
late final IDebugService debugService; late final IDebugService debugService;
/// Сервис для работы с путями
late final IPathProvider pathProvider;
/// Конфигурация приложения /// Конфигурация приложения
late final IAppConfig appConfig; late final IAppConfig appConfig;
/// Сервис для работы с локальным хранилищем
late final ISecureStorage secureStorage;
/// Сервис для работы с HTTP запросами /// Сервис для работы с HTTP запросами
late final IHttpClient httpClient; late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
/// Репозитории приложения
late final DiRepositories repositories; late final DiRepositories repositories;
/// Сервисы приложения
late final DiServices services;
/// Метод для инициализации зависимостей /// Метод для инициализации зависимостей
Future<void> init({ Future<void> init({
required OnProgress onProgress, required OnProgress onProgress,
required OnComplete onComplete, required OnComplete onComplete,
required OnError onError, required OnError onError,
}) async { }) async {
// Инициализация сервисов // Инициализация конфигурации приложения
await _initServices(
onComplete: onComplete,
onError: onError,
onProgress: onProgress,
);
// Инициализация репозиториев
repositories = DiRepositories();
repositories.init(
onProgress: onProgress,
onComplete: onComplete,
onError: onError,
diContainer: this,
);
onComplete('Инициализация зависимостей завершена!');
}
/// Метод для инициализации сервисов
Future<void> _initServices({
required OnComplete onComplete,
required OnError onError,
required OnProgress onProgress,
}) async {
appConfig = switch (env) { appConfig = switch (env) {
AppEnv.dev => AppConfigDev(), AppEnv.dev => AppConfigDev(),
AppEnv.prod => AppConfigProd(), AppEnv.prod => AppConfigProd(),
AppEnv.stage => AppConfigStage() AppEnv.stage => AppConfigStage()
}; };
httpClient = AppHttpClient( // Инициализация HTTP клиента
debugService: debugService, httpClientFactory = (debugService, appConfig) => AppHttpClient(
appConfig: appConfig, debugService: debugService,
); appConfig: appConfig,
);
try { // Инициализация сервисов
pathProvider = AppPathProvider(); services = DiServices()
onProgress(AppPathProvider.name); ..init(
} on Object catch (error, stackTrace) { onProgress: onProgress,
onError( onError: onError,
'Ошибка инициализации ${IPathProvider.name}', diContainer: this,
error, );
stackTrace, // throw Exception('Тестовая - ошибка инициализации зависимостей');
// Инициализация репозиториев
repositories = DiRepositories()
..init(
onProgress: onProgress,
onError: onError,
diContainer: this,
); );
}
try { onComplete('Инициализация зависимостей завершена!');
secureStorage = AppSecureStorage(secretKey: appConfig.secretKey);
onProgress(AppSecureStorage.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации ${ISecureStorage.name}',
error,
stackTrace,
);
}
} }
} }

View File

@@ -38,20 +38,21 @@ final class DiRepositories {
/// ///
/// Принимает: /// Принимает:
/// - [onProgress] - обратный вызов при прогрессе /// - [onProgress] - обратный вызов при прогрессе
/// - [onComplete] - обратный вызов при успешной инициализации
/// - [diContainer] - контейнер зависимостей /// - [diContainer] - контейнер зависимостей
void init({ void init({
required OnProgress onProgress, required OnProgress onProgress,
required OnComplete onComplete,
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
try { try {
//Инициализация репозитория авторизации //Инициализация репозитория авторизации
authRepository = lazyInitRepo<IAuthRepository>( authRepository = _lazyInitRepo<IAuthRepository>(
mockFactory: AuthMockRepository.new, mockFactory: AuthMockRepository.new,
mainFactory: () => AuthRepository( mainFactory: () => AuthRepository(
httpClient: diContainer.httpClient, httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
), ),
onProgress: onProgress, onProgress: onProgress,
environment: diContainer.env, environment: diContainer.env,
@@ -67,10 +68,13 @@ final class DiRepositories {
try { try {
// Инициализация репозитория сервиса управления токеном доступа // Инициализация репозитория сервиса управления токеном доступа
mainRepository = lazyInitRepo<IMainRepository>( mainRepository = _lazyInitRepo<IMainRepository>(
mockFactory: MainMockRepository.new, mockFactory: MainMockRepository.new,
mainFactory: () => MainRepository( mainFactory: () => MainRepository(
httpClient: diContainer.httpClient, httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
), ),
onProgress: onProgress, onProgress: onProgress,
environment: diContainer.env, environment: diContainer.env,
@@ -84,7 +88,7 @@ final class DiRepositories {
); );
} }
onComplete( onProgress(
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})', 'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
); );
} }
@@ -96,18 +100,22 @@ final class DiRepositories {
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами /// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий /// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
/// - [onProgress] - обратный вызов при прогрессе /// - [onProgress] - обратный вызов при прогрессе
T lazyInitRepo<T extends DiBaseRepo>({ T _lazyInitRepo<T extends DiBaseRepo>({
required AppEnv environment, required AppEnv environment,
required T Function() mainFactory, required T Function() mainFactory,
required T Function() mockFactory, required T Function() mockFactory,
required OnProgress onProgress, required OnProgress onProgress,
}) { }) {
final mockRepo = mockFactory();
final mainRepo = mainFactory();
final repo = switch (environment) { final repo = switch (environment) {
AppEnv.dev => mockFactory(), AppEnv.dev => mockRepo,
AppEnv.prod => mainFactory(), AppEnv.prod => mainRepo,
AppEnv.stage => AppEnv.stage =>
_mockReposToSwitch.contains(mockFactory().name) ? mockFactory() : mainFactory(), _mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
}; };
onProgress(repo.name); onProgress(repo.name);
return repo; return repo;
} }

50
lib/di/di_services.dart Normal file
View File

@@ -0,0 +1,50 @@
import 'package:app_services/app_services.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/di/di_typedefs.dart';
import 'package:i_app_services/i_app_services.dart';
/// Класс для инициализации сервисов
final class DiServices {
/// Сервис для работы с путями
late final IPathProvider pathProvider;
/// Сервис для работы с локальным хранилищем
late final ISecureStorage secureStorage;
/// Метод для инициализации репозиториев в приложении
///
/// Принимает:
/// - [onProgress] - обратный вызов при прогрессе
/// - [diContainer] - контейнер зависимостей
/// - [onError] - обратный вызов при ошибке
void init({
required OnProgress onProgress,
required OnError onError,
required DiContainer diContainer,
}) {
try {
pathProvider = AppPathProvider();
onProgress(AppPathProvider.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации ${IPathProvider.name}',
error,
stackTrace,
);
}
try {
secureStorage = AppSecureStorage(
secretKey: diContainer.appConfig.secretKey,
);
onProgress(AppSecureStorage.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации ${ISecureStorage.name}',
error,
stackTrace,
);
}
onProgress('Инициализация сервисов завершена!');
}
}

View File

@@ -16,7 +16,7 @@ class DebugScreen extends StatelessWidget {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
Text( Text(
'Реализация SecureStorage: ${context.di.secureStorage.nameImpl}', 'Реализация SecureStorage: ${context.di.services.secureStorage.nameImpl}',
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
@@ -82,14 +82,37 @@ class DebugScreen extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('Тестовая иконка из assets'), const Text('Тестовая иконка из assets'),
const SizedBox(height: 16),
Assets.icons.home.svg(), Assets.icons.home.svg(),
ElevatedButton(
onPressed: () {
throw Exception(
'Тестовая ошибка Exception для отладки FlutterError',
);
},
child: const Text('Вызывать ошибку FlutterError'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
await _callError();
},
child: const Text('Вызывать ошибку PlatformDispatcher'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
await context.di.debugService.openDebugScreen(context);
},
child: const Text('Вызывать Экран отладки'),
),
], ],
), ),
), ),
); );
} }
Future<void> callError() async { Future<void> _callError() async {
throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher'); throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher');
} }
} }

View File

@@ -5,16 +5,42 @@ import 'package:flutter/material.dart';
/// {@endtemplate} /// {@endtemplate}
class ErrorScreen extends StatelessWidget { class ErrorScreen extends StatelessWidget {
/// {@macro ErrorScreen} /// {@macro ErrorScreen}
const ErrorScreen({super.key}); const ErrorScreen({
super.key,
required this.error,
required this.stackTrace,
this.onRetry,
});
final Object? error;
final StackTrace? stackTrace;
final VoidCallback? onRetry;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const MaterialApp( return MaterialApp(
home: Scaffold( home: Scaffold(
body: Center( body: SafeArea(
child: Text( child: Center(
'Что-то пошло не так, попробуйте перезагрузить приложение', child: ListView(
textAlign: TextAlign.center, padding: const EdgeInsets.all(16),
children: [
const SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: const Text('Перезагрузить приложение'),
),
const SizedBox(height: 16),
Text(
'''
Что-то пошло не так, попробуйте перезагрузить приложение
error: $error
stackTrace: $stackTrace
''',
textAlign: TextAlign.center,
),
],
),
), ),
), ),
), ),

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:friflex_starter/gen/assets.gen.dart';
/// {@template SplashScreen}
/// Экран загрузки приложения.
/// {@endtemplate}
class SplashScreen extends StatelessWidget {
/// {@macro SplashScreen}
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Assets.lottie.splash.lottie(),
);
}
}

View File

@@ -10,6 +10,7 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart' as _svg; import 'package:flutter_svg/flutter_svg.dart' as _svg;
import 'package:lottie/lottie.dart' as _lottie;
import 'package:vector_graphics/vector_graphics.dart' as _vg; import 'package:vector_graphics/vector_graphics.dart' as _vg;
class $AssetsFontsGen { class $AssetsFontsGen {
@@ -50,11 +51,23 @@ class $AssetsIconsGen {
List<SvgGenImage> get values => [home]; List<SvgGenImage> get values => [home];
} }
class $AssetsLottieGen {
const $AssetsLottieGen();
/// File path: assets/lottie/splash.json
LottieGenImage get splash =>
const LottieGenImage('assets/lottie/splash.json');
/// List of all assets
List<LottieGenImage> get values => [splash];
}
class Assets { class Assets {
Assets._(); Assets._();
static const $AssetsFontsGen fonts = $AssetsFontsGen(); static const $AssetsFontsGen fonts = $AssetsFontsGen();
static const $AssetsIconsGen icons = $AssetsIconsGen(); static const $AssetsIconsGen icons = $AssetsIconsGen();
static const $AssetsLottieGen lottie = $AssetsLottieGen();
} }
class SvgGenImage { class SvgGenImage {
@@ -133,3 +146,70 @@ class SvgGenImage {
String get keyName => _assetName; String get keyName => _assetName;
} }
class LottieGenImage {
const LottieGenImage(
this._assetName, {
this.flavors = const {},
});
final String _assetName;
final Set<String> flavors;
_lottie.LottieBuilder lottie({
Animation<double>? controller,
bool? animate,
_lottie.FrameRate? frameRate,
bool? repeat,
bool? reverse,
_lottie.LottieDelegates? delegates,
_lottie.LottieOptions? options,
void Function(_lottie.LottieComposition)? onLoaded,
_lottie.LottieImageProviderFactory? imageProviderFactory,
Key? key,
AssetBundle? bundle,
Widget Function(
BuildContext,
Widget,
_lottie.LottieComposition?,
)? frameBuilder,
ImageErrorWidgetBuilder? errorBuilder,
double? width,
double? height,
BoxFit? fit,
AlignmentGeometry? alignment,
String? package,
bool? addRepaintBoundary,
FilterQuality? filterQuality,
void Function(String)? onWarning,
}) {
return _lottie.Lottie.asset(
_assetName,
controller: controller,
animate: animate,
frameRate: frameRate,
repeat: repeat,
reverse: reverse,
delegates: delegates,
options: options,
onLoaded: onLoaded,
imageProviderFactory: imageProviderFactory,
key: key,
bundle: bundle,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
width: width,
height: height,
fit: fit,
alignment: alignment,
package: package,
addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning,
);
}
String get path => _assetName;
String get keyName => _assetName;
}

View File

@@ -3,6 +3,7 @@ import 'package:friflex_starter/features/debug/debug_routes.dart';
import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart';
import 'package:friflex_starter/features/main/presentation/main_routes.dart'; import 'package:friflex_starter/features/main/presentation/main_routes.dart';
import 'package:friflex_starter/features/root/root_screen.dart'; import 'package:friflex_starter/features/root/root_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// Класс, реализующий роутер приложения и все поля классов /// Класс, реализующий роутер приложения и все поля классов
@@ -14,7 +15,7 @@ class AppRouter {
static final rootNavigatorKey = GlobalKey<NavigatorState>(); static final rootNavigatorKey = GlobalKey<NavigatorState>();
/// Начальный роут приложения /// Начальный роут приложения
static String get initialLocation => '/debug'; static String get initialLocation => '/debug';
/// Метод для создания экземпляра GoRouter /// Метод для создания экземпляра GoRouter
static GoRouter createRouter(IDebugService debugService) { static GoRouter createRouter(IDebugService debugService) {
@@ -32,6 +33,10 @@ class AppRouter {
DebugRoutes.buildShellBranch(), DebugRoutes.buildShellBranch(),
], ],
), ),
GoRoute(
path: '/splash',
builder: (context, state) => const SplashScreen(),
),
], ],
); );
} }

View File

@@ -15,6 +15,11 @@ import 'package:go_router/go_router.dart';
part 'errors_handlers.dart'; part 'errors_handlers.dart';
/// Время ожидания инициализации зависимостей
/// Если время превышено, то будет показан экран ошибки
/// В дальнейшем нужно убрать в env
const _initTimeout = Duration(seconds: 7);
/// Класс, реализующий раннер для конфигурирования приложения при запуске /// Класс, реализующий раннер для конфигурирования приложения при запуске
/// ///
/// Порядок инициализации: /// Порядок инициализации:
@@ -33,38 +38,63 @@ class AppRunner {
final AppEnv env; final AppEnv env;
/// Контейнер зависимостей приложения /// Контейнер зависимостей приложения
late final IDebugService _debugService; late IDebugService _debugService;
/// Роутер приложения /// Роутер приложения
late final GoRouter router; late GoRouter router;
/// Таймер для отслеживания времени инициализации приложения /// Таймер для отслеживания времени инициализации приложения
late final TimerRunner _timerRunner; late TimerRunner _timerRunner;
/// Метод для запуска приложения /// Метод для запуска приложения
Future<void> run() async { Future<void> run() async {
WidgetsFlutterBinding.ensureInitialized(); try {
// Инициализация сервиса отладки WidgetsFlutterBinding.ensureInitialized();
_debugService = DebugService(); // Инициализация сервиса отладки
_debugService = DebugService();
_timerRunner = TimerRunner(_debugService); _timerRunner = TimerRunner(_debugService);
// Инициализация приложения // Инициализация приложения
await _initApp(); await _initApp();
// Инициализация метода обработки ошибок // Инициализация метода обработки ошибок
_initErrorHandlers(_debugService); _initErrorHandlers(_debugService);
// Инициализация репозиториев и сервисов // Инициализация роутера
final diContainer = await _initDependencies(_debugService); router = AppRouter.createRouter(_debugService);
// Инициализация роутера // throw Exception('Test error');
router = AppRouter.createRouter(_debugService);
runApp( runApp(
App(diContainer: diContainer, router: router), App(
); router: router,
await _onAppLoaded(); initDependencies: () {
return _initDependencies(
debugService: _debugService,
env: env,
timerRunner: _timerRunner,
).timeout(
_initTimeout,
onTimeout: () {
return Future.error(
TimeoutException(
'Превышено время ожидания инициализации зависимостей',
),
);
},
);
},
),
);
await _onAppLoaded();
} on Object catch (e, stackTrace) {
await _onAppLoaded();
/// Если произошла ошибка при инициализации приложения,
/// то запускаем экран ошибки
runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run));
}
} }
/// Метод инициализации приложения, /// Метод инициализации приложения,
@@ -85,23 +115,36 @@ class AppRunner {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.allowFirstFrame(); WidgetsBinding.instance.allowFirstFrame();
}); });
_timerRunner.stop();
} }
/// Метод для инициализации зависимостей приложения // Метод для инициализации зависимостей приложения
Future<DiContainer> _initDependencies(IDebugService debugService) async { Future<DiContainer> _initDependencies({
required IDebugService debugService,
required AppEnv env,
required TimerRunner timerRunner,
}) async {
// Имитация задержки инициализации
// TODO(yura): Удалить после проверки
await Future.delayed(const Duration(seconds: 3));
debugService.log(() => 'Тип сборки: ${env.name}'); debugService.log(() => 'Тип сборки: ${env.name}');
final diContainer = DiContainer( final diContainer = DiContainer(
env: env, env: env,
dService: debugService, dService: debugService,
); );
await diContainer.init( await diContainer.init(
onProgress: _timerRunner.logOnProgress, onProgress: (name) => timerRunner.logOnProgress(name),
onComplete: _timerRunner.logOnComplete, onComplete: (name) {
onError: _timerRunner.logOnError, timerRunner
..logOnComplete(name)
..stop();
},
onError: (message, error, [stackTrace]) => debugService.logError(
message,
error: error,
stackTrace: stackTrace,
),
); );
//throw Exception('Test error');
return diContainer; return diContainer;
} }
} }

View File

@@ -4,7 +4,7 @@ part of 'app_runner.dart';
void _initErrorHandlers(IDebugService debugService) { void _initErrorHandlers(IDebugService debugService) {
// Обработка ошибок в приложении // Обработка ошибок в приложении
FlutterError.onError = (details) { FlutterError.onError = (details) {
_showErrorScreen(); _showErrorScreen(details.exception, details.stack);
debugService.logError( debugService.logError(
() => 'FlutterError.onError: ${details.exceptionAsString()}', () => 'FlutterError.onError: ${details.exceptionAsString()}',
error: details.exception, error: details.exception,
@@ -13,7 +13,7 @@ void _initErrorHandlers(IDebugService debugService) {
}; };
// Обработка асинхронных ошибок в приложении // Обработка асинхронных ошибок в приложении
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
_showErrorScreen(); _showErrorScreen(error, stack);
debugService.logError( debugService.logError(
() => 'PlatformDispatcher.instance.onError', () => 'PlatformDispatcher.instance.onError',
error: error, error: error,
@@ -24,11 +24,11 @@ void _initErrorHandlers(IDebugService debugService) {
} }
/// Метод для показа экрана ошибки /// Метод для показа экрана ошибки
void _showErrorScreen() { void _showErrorScreen(Object error, StackTrace? stackTrace) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
AppRouter.rootNavigatorKey.currentState?.push( AppRouter.rootNavigatorKey.currentState?.push(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => const ErrorScreen(), builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
), ),
); );
}); });

View File

@@ -36,6 +36,7 @@ class TimerRunner {
_debugService.log( _debugService.log(
'$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс', '$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс',
); );
} }
/// Метод для обработки прогресса инициализации зависимостей /// Метод для обработки прогресса инициализации зависимостей

View File

@@ -29,6 +29,14 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" version: "0.0.1"
archive:
dependency: transitive
description:
name: archive
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -585,6 +593,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
url: "https://pub.dev"
source: hosted
version: "3.3.1"
macros: macros:
dependency: transitive dependency: transitive
description: description:
@@ -754,6 +770,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
posix:
dependency: transitive
description:
name: posix
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
url: "https://pub.dev"
source: hosted
version: "6.0.1"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -993,4 +1017,4 @@ packages:
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.6.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View File

@@ -21,6 +21,7 @@ dependencies:
flutter_svg: 2.0.17 flutter_svg: 2.0.17
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
lottie: 3.3.1
### основной сервис с интерфейсами ### основной сервис с интерфейсами
i_app_services: i_app_services:
@@ -51,6 +52,7 @@ flutter:
- assets/icons/ - assets/icons/
- assets/fonts/ - assets/fonts/
- assets/images/ - assets/images/
- assets/lottie/
fonts: fonts:
- family: Montserrat - family: Montserrat
@@ -72,4 +74,5 @@ flutter_gen:
line_length: 100 line_length: 100
integrations: integrations:
flutter_svg: true flutter_svg: true
lottie: true