Skip to content

Commit 7cf9fe1

Browse files
authored
Merge pull request #122 from flutter-news-app-full-source-code/fix-demo-env-related-errors
Fix demo env related errors
2 parents 1a8386c + d0f5d9c commit 7cf9fe1

File tree

10 files changed

+232
-143
lines changed

10 files changed

+232
-143
lines changed

lib/app/bloc/app_bloc.dart

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,17 @@ class AppBloc extends Bloc<AppEvent, AppState> {
143143
'[AppBloc] Failed to fetch user settings (HttpException) '
144144
'for user ${user.id}: ${e.runtimeType} - ${e.message}',
145145
);
146-
emit(
147-
state.copyWith(
148-
status: AppLifeCycleStatus.criticalError,
149-
initialUserPreferencesError: e,
150-
),
151-
);
146+
// In demo mode, NotFoundException for user settings is expected if not yet initialized.
147+
// Do not transition to criticalError immediately.
148+
if (_environment != local_config.AppEnvironment.demo ||
149+
e is! NotFoundException) {
150+
emit(
151+
state.copyWith(
152+
status: AppLifeCycleStatus.criticalError,
153+
initialUserPreferencesError: e,
154+
),
155+
);
156+
}
152157
} catch (e, s) {
153158
_logger.severe(
154159
'[AppBloc] Unexpected error during user settings fetch '
@@ -185,12 +190,17 @@ class AppBloc extends Bloc<AppEvent, AppState> {
185190
'[AppBloc] Failed to fetch user content preferences (HttpException) '
186191
'for user ${user.id}: ${e.runtimeType} - ${e.message}',
187192
);
188-
emit(
189-
state.copyWith(
190-
status: AppLifeCycleStatus.criticalError,
191-
initialUserPreferencesError: e,
192-
),
193-
);
193+
// In demo mode, NotFoundException for user content preferences is expected if not yet initialized.
194+
// Do not transition to criticalError immediately.
195+
if (_environment != local_config.AppEnvironment.demo ||
196+
e is! NotFoundException) {
197+
emit(
198+
state.copyWith(
199+
status: AppLifeCycleStatus.criticalError,
200+
initialUserPreferencesError: e,
201+
),
202+
);
203+
}
194204
} catch (e, s) {
195205
_logger.severe(
196206
'[AppBloc] Unexpected error during user content preferences fetch '
@@ -313,46 +323,86 @@ class AppBloc extends Bloc<AppEvent, AppState> {
313323

314324
emit(state.copyWith(status: newStatus));
315325

316-
// If a new user is present, fetch their specific data.
326+
// If a new user is present, handle their data.
317327
if (newUser != null) {
318-
await _fetchAndSetUserData(newUser, emit);
319-
} else {
320-
// If user logs out, clear user-specific data from state.
321-
emit(state.copyWith(settings: null, userContentPreferences: null));
322-
}
323-
324-
// In demo mode, ensure user-specific data is initialized.
325-
if (_environment == local_config.AppEnvironment.demo &&
326-
demoDataInitializerService != null &&
327-
newUser != null) {
328-
try {
329-
_logger.info('Demo mode: Initializing data for user ${newUser.id}.');
330-
await demoDataInitializerService!.initializeUserSpecificData(newUser);
328+
// In demo mode, ensure essential user-specific data (settings,
329+
// preferences, and the user object itself in the data client)
330+
// are initialized if they don't already exist. This prevents
331+
// NotFoundException during subsequent reads.
332+
if (_environment == local_config.AppEnvironment.demo &&
333+
demoDataInitializerService != null) {
331334
_logger.info(
332-
'Demo mode: Data initialization complete for ${newUser.id}.',
335+
'[AppBloc] Demo mode: Initializing user-specific data for '
336+
'user: ${newUser.id}',
333337
);
334-
} catch (e, s) {
335-
_logger.severe('ERROR: Failed to initialize demo user data.', e, s);
338+
try {
339+
await demoDataInitializerService!.initializeUserSpecificData(newUser);
340+
_logger.info(
341+
'[AppBloc] Demo mode: User-specific data initialized for '
342+
'user: ${newUser.id}.',
343+
);
344+
} catch (e, s) {
345+
_logger.severe(
346+
'[AppBloc] ERROR: Failed to initialize demo user data.',
347+
e,
348+
s,
349+
);
350+
emit(
351+
state.copyWith(
352+
status: AppLifeCycleStatus.criticalError,
353+
initialUserPreferencesError: UnknownException(
354+
'Failed to initialize demo user data: $e',
355+
),
356+
),
357+
);
358+
return; // Stop further processing if initialization failed critically.
359+
}
336360
}
337-
}
338361

339-
// Handle data migration if an anonymous user signs in.
340-
if (oldUser != null &&
341-
oldUser.appRole == AppUserRole.guestUser &&
342-
newUser != null &&
343-
newUser.appRole == AppUserRole.standardUser) {
344-
_logger.info(
345-
'Anonymous user ${oldUser.id} transitioned to authenticated user '
346-
'${newUser.id}. Attempting data migration.',
347-
);
348-
if (demoDataMigrationService != null &&
349-
_environment == local_config.AppEnvironment.demo) {
350-
await demoDataMigrationService!.migrateAnonymousData(
351-
oldUserId: oldUser.id,
352-
newUserId: newUser.id,
362+
// Handle data migration if an anonymous user signs in.
363+
if (oldUser != null &&
364+
oldUser.appRole == AppUserRole.guestUser &&
365+
newUser.appRole == AppUserRole.standardUser) {
366+
_logger.info(
367+
'[AppBloc] Anonymous user ${oldUser.id} transitioned to '
368+
'authenticated user ${newUser.id}. Attempting data migration.',
353369
);
354-
_logger.info('Demo mode: Data migration completed for ${newUser.id}.');
370+
if (demoDataMigrationService != null &&
371+
_environment == local_config.AppEnvironment.demo) {
372+
try {
373+
await demoDataMigrationService!.migrateAnonymousData(
374+
oldUserId: oldUser.id,
375+
newUserId: newUser.id,
376+
);
377+
_logger.info(
378+
'[AppBloc] Demo mode: Data migration completed for ${newUser.id}.',
379+
);
380+
} catch (e, s) {
381+
_logger.severe(
382+
'[AppBloc] ERROR: Failed to migrate demo user data.',
383+
e,
384+
s,
385+
);
386+
// If demo data migration fails, it's a critical error for demo mode.
387+
emit(
388+
state.copyWith(
389+
status: AppLifeCycleStatus.criticalError,
390+
initialUserPreferencesError: UnknownException(
391+
'Failed to migrate demo user data: $e',
392+
),
393+
),
394+
);
395+
return; // Stop further processing if migration failed critically.
396+
}
397+
}
355398
}
399+
400+
// After potential initialization and migration,
401+
// ensure user-specific data (settings and preferences) are loaded.
402+
await _fetchAndSetUserData(newUser, emit);
403+
} else {
404+
// If user logs out, clear user-specific data from state.
405+
emit(state.copyWith(settings: null, userContentPreferences: null));
356406
}
357407
}
358408

@@ -406,6 +456,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
406456

407457
final updatedSettings = event.settings;
408458

459+
// Optimistically update the state.
409460
emit(state.copyWith(settings: updatedSettings));
410461

411462
try {

lib/app/services/demo_data_initializer_service.dart

Lines changed: 32 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,51 @@
11
import 'package:core/core.dart';
22
import 'package:data_repository/data_repository.dart';
3-
// Required for ThemeMode, AppBaseTheme, etc.
3+
import 'package:logging/logging.dart'; // Import Logger
44

55
/// {@template demo_data_initializer_service}
66
/// A service responsible for ensuring that essential user-specific data
7-
/// (like [UserAppSettings], [UserContentPreferences], and the [User] object
8-
/// itself) exists in the data in-memory clients when a user is first encountered
9-
/// in the demo environment.
7+
/// (like [UserAppSettings] and [UserContentPreferences]) exists in the
8+
/// data in-memory clients when a user is first encountered in the demo environment.
109
///
1110
/// This service is specifically designed for the in-memory data clients
1211
/// used in the demo environment. In production/development environments,
1312
/// the backend API is responsible for initializing user data.
1413
/// {@endtemplate}
1514
class DemoDataInitializerService {
1615
/// {@macro demo_data_initializer_service}
17-
const DemoDataInitializerService({
16+
DemoDataInitializerService({
1817
required DataRepository<UserAppSettings> userAppSettingsRepository,
1918
required DataRepository<UserContentPreferences>
2019
userContentPreferencesRepository,
21-
required DataRepository<User> userRepository,
2220
}) : _userAppSettingsRepository = userAppSettingsRepository,
2321
_userContentPreferencesRepository = userContentPreferencesRepository,
24-
_userRepository = userRepository;
22+
_logger = Logger('DemoDataInitializerService'); // Initialize logger
2523

2624
final DataRepository<UserAppSettings> _userAppSettingsRepository;
2725
final DataRepository<UserContentPreferences>
2826
_userContentPreferencesRepository;
29-
final DataRepository<User> _userRepository;
27+
final Logger _logger; // Add logger instance
3028

3129
/// Initializes essential user-specific data in the in-memory clients
3230
/// for the given [user].
3331
///
34-
/// This method checks if [UserAppSettings], [UserContentPreferences],
35-
/// and the [User] object itself exist for the provided user ID. If any
36-
/// are missing, it creates them with default values.
32+
/// This method checks if [UserAppSettings] and [UserContentPreferences]
33+
/// exist for the provided user ID. If any are missing, it creates them
34+
/// with default values.
3735
///
3836
/// This prevents "READ FAILED" errors when the application attempts to
3937
/// access these user-specific data points for a newly signed-in anonymous
4038
/// user in the demo environment.
4139
Future<void> initializeUserSpecificData(User user) async {
42-
print(
43-
'[DemoDataInitializerService] Initializing user-specific data for '
44-
'user ID: ${user.id}',
45-
);
40+
_logger.info('Initializing user-specific data for user ID: ${user.id}');
4641

4742
await Future.wait([
4843
_ensureUserAppSettingsExist(user.id),
4944
_ensureUserContentPreferencesExist(user.id),
50-
_ensureUserClientUserExists(user),
5145
]);
5246

53-
print(
54-
'[DemoDataInitializerService] User-specific data initialization '
55-
'completed for user ID: ${user.id}',
47+
_logger.info(
48+
'User-specific data initialization completed for user ID: ${user.id}',
5649
);
5750
}
5851

@@ -61,12 +54,10 @@ class DemoDataInitializerService {
6154
Future<void> _ensureUserAppSettingsExist(String userId) async {
6255
try {
6356
await _userAppSettingsRepository.read(id: userId, userId: userId);
64-
print(
65-
'[DemoDataInitializerService] UserAppSettings found for user ID: $userId.',
66-
);
57+
_logger.info('UserAppSettings found for user ID: $userId.');
6758
} on NotFoundException {
68-
print(
69-
'[DemoDataInitializerService] UserAppSettings not found for user ID: '
59+
_logger.info(
60+
'UserAppSettings not found for user ID: '
7061
'$userId. Creating default settings.',
7162
);
7263
final defaultSettings = UserAppSettings(
@@ -95,14 +86,16 @@ class DemoDataInitializerService {
9586
item: defaultSettings,
9687
userId: userId,
9788
);
98-
print(
99-
'[DemoDataInitializerService] Default UserAppSettings created for '
89+
_logger.info(
90+
'Default UserAppSettings created for '
10091
'user ID: $userId.',
10192
);
10293
} catch (e, s) {
103-
print(
104-
'[DemoDataInitializerService] Error ensuring UserAppSettings exist '
105-
'for user ID: $userId: $e\n$s',
94+
_logger.severe(
95+
'Error ensuring UserAppSettings exist '
96+
'for user ID: $userId: $e',
97+
e,
98+
s,
10699
);
107100
rethrow;
108101
}
@@ -113,12 +106,10 @@ class DemoDataInitializerService {
113106
Future<void> _ensureUserContentPreferencesExist(String userId) async {
114107
try {
115108
await _userContentPreferencesRepository.read(id: userId, userId: userId);
116-
print(
117-
'[DemoDataInitializerService] UserContentPreferences found for user ID: $userId.',
118-
);
109+
_logger.info('UserContentPreferences found for user ID: $userId.');
119110
} on NotFoundException {
120-
print(
121-
'[DemoDataInitializerService] UserContentPreferences not found for '
111+
_logger.info(
112+
'UserContentPreferences not found for '
122113
'user ID: $userId. Creating default preferences.',
123114
);
124115
final defaultPreferences = UserContentPreferences(
@@ -132,48 +123,16 @@ class DemoDataInitializerService {
132123
item: defaultPreferences,
133124
userId: userId,
134125
);
135-
print(
136-
'[DemoDataInitializerService] Default UserContentPreferences created '
126+
_logger.info(
127+
'Default UserContentPreferences created '
137128
'for user ID: $userId.',
138129
);
139130
} catch (e, s) {
140-
print(
141-
'[DemoDataInitializerService] Error ensuring UserContentPreferences '
142-
'exist for user ID: $userId: $e\n$s',
143-
);
144-
rethrow;
145-
}
146-
}
147-
148-
/// Ensures that the [User] object for the given [user] exists in the
149-
/// user client. If not found, creates it. If found, updates it.
150-
///
151-
/// This is important because the `AuthInmemory` client might create a
152-
/// basic user, but the `DataInMemory<User>` client might not have it
153-
/// immediately.
154-
Future<void> _ensureUserClientUserExists(User user) async {
155-
try {
156-
await _userRepository.read(id: user.id, userId: user.id);
157-
// If user exists, ensure it's up-to-date (e.g., if roles changed)
158-
await _userRepository.update(id: user.id, item: user, userId: user.id);
159-
print(
160-
'[DemoDataInitializerService] User object found and updated in '
161-
'user client for ID: ${user.id}.',
162-
);
163-
} on NotFoundException {
164-
print(
165-
'[DemoDataInitializerService] User object not found in user client '
166-
'for ID: ${user.id}. Creating it.',
167-
);
168-
await _userRepository.create(item: user, userId: user.id);
169-
print(
170-
'[DemoDataInitializerService] User object created in user client '
171-
'for ID: ${user.id}.',
172-
);
173-
} catch (e, s) {
174-
print(
175-
'[DemoDataInitializerService] Error ensuring User object exists in '
176-
'user client for ID: ${user.id}: $e\n$s',
131+
_logger.severe(
132+
'Error ensuring UserContentPreferences '
133+
'exist for user ID: $userId: $e',
134+
e,
135+
s,
177136
);
178137
rethrow;
179138
}

0 commit comments

Comments
 (0)