refactor: stabilize app bootstrap and polling
This commit is contained in:
108
lib/app/app_bootstrap.dart
Normal file
108
lib/app/app_bootstrap.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../providers/providers.dart';
|
||||
|
||||
enum AppBootstrapStatus {
|
||||
bootstrapping,
|
||||
noHomes,
|
||||
ready,
|
||||
authFailed,
|
||||
networkFailed,
|
||||
invalidConfig,
|
||||
failed,
|
||||
}
|
||||
|
||||
class AppBootstrapState {
|
||||
final AppBootstrapStatus status;
|
||||
final String? message;
|
||||
|
||||
const AppBootstrapState._(this.status, {this.message});
|
||||
|
||||
const AppBootstrapState.bootstrapping()
|
||||
: this._(AppBootstrapStatus.bootstrapping);
|
||||
|
||||
const AppBootstrapState.noHomes() : this._(AppBootstrapStatus.noHomes);
|
||||
|
||||
const AppBootstrapState.ready() : this._(AppBootstrapStatus.ready);
|
||||
|
||||
const AppBootstrapState.authFailed(String message)
|
||||
: this._(AppBootstrapStatus.authFailed, message: message);
|
||||
|
||||
const AppBootstrapState.networkFailed(String message)
|
||||
: this._(AppBootstrapStatus.networkFailed, message: message);
|
||||
|
||||
const AppBootstrapState.invalidConfig(String message)
|
||||
: this._(AppBootstrapStatus.invalidConfig, message: message);
|
||||
|
||||
const AppBootstrapState.failed(String message)
|
||||
: this._(AppBootstrapStatus.failed, message: message);
|
||||
|
||||
bool get isBootstrapping => status == AppBootstrapStatus.bootstrapping;
|
||||
}
|
||||
|
||||
final appBootstrapProvider =
|
||||
NotifierProvider<AppBootstrapNotifier, AppBootstrapState>(
|
||||
AppBootstrapNotifier.new,
|
||||
);
|
||||
|
||||
class AppBootstrapNotifier extends Notifier<AppBootstrapState> {
|
||||
@override
|
||||
AppBootstrapState build() => const AppBootstrapState.bootstrapping();
|
||||
|
||||
Future<void> bootstrap() async {
|
||||
state = const AppBootstrapState.bootstrapping();
|
||||
|
||||
try {
|
||||
await ref.read(homesProvider.notifier).load();
|
||||
await ref.read(currentHomeProvider.notifier).load();
|
||||
|
||||
final home = ref.read(currentHomeProvider);
|
||||
if (home == null) {
|
||||
state = const AppBootstrapState.noHomes();
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.read(authInfoProvider.notifier).load(failOnError: true);
|
||||
await syncGeofenceTask(ref.read(homesProvider));
|
||||
|
||||
state = const AppBootstrapState.ready();
|
||||
} catch (e) {
|
||||
state = _failureState(e);
|
||||
}
|
||||
}
|
||||
|
||||
AppBootstrapState _failureState(Object error) {
|
||||
if (error is StateError || error is FormatException) {
|
||||
return AppBootstrapState.invalidConfig(error.toString());
|
||||
}
|
||||
|
||||
if (error is DioException) {
|
||||
final statusCode = error.response?.statusCode;
|
||||
if (statusCode == 401 || statusCode == 403) {
|
||||
return AppBootstrapState.authFailed(
|
||||
'API key не прошёл авторизацию ($statusCode)',
|
||||
);
|
||||
}
|
||||
|
||||
if (_isNetworkFailure(error)) {
|
||||
return AppBootstrapState.networkFailed(
|
||||
'Backend недоступен: ${error.message ?? error.type.name}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return AppBootstrapState.failed(error.toString());
|
||||
}
|
||||
|
||||
bool _isNetworkFailure(DioException error) {
|
||||
return switch (error.type) {
|
||||
DioExceptionType.connectionTimeout ||
|
||||
DioExceptionType.sendTimeout ||
|
||||
DioExceptionType.receiveTimeout ||
|
||||
DioExceptionType.connectionError ||
|
||||
DioExceptionType.unknown => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user