I currently face an issue where I route to an authentication guard view as my default route. My authentication guard view:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/user.dart';
import '../services/services.module.dart';
import '../widgets/common/async_stream.dart';
import 'landing_screen/landing_screen.dart';
import 'tabs_screen/tab_screen.dart';
/// The [ViewAuthGuard] decides whether to display the [LandingScreenView] or the [TabsScreenView].
class ViewAuthGuard extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('ViewAuthGuard build called: $context');
FirebaseAuthService authService = Provider.of<AuthService>(context, listen: false);
return AsyncStreamWidget<User>(
stream: authService.onAuthStateChanged,
child: (User user) => TabsScreenView(),
emptyWidget: LandingScreenView(),
loadingWidget: null,
errorWidget: null,
);
}
}
and my AsyncStreamWidget
:
import 'package:flutter/material.dart';
import '../../../models/base_model.dart';
import '../../error/future_error.dart';
import '../../loading.dart';
class AsyncStreamWidget<T extends BaseModel> extends StatelessWidget {
final Stream<T> stream;
final T initialData;
Widget _loading;
Widget _empty;
Widget Function(Object) _error;
Widget Function(T) child;
AsyncStreamWidget({
@required this.stream,
@required this.child,
this.initialData,
Widget loadingWidget,
Widget emptyWidget,
Widget Function(Object) errorWidget,
}) {
if (loadingWidget == null) {
_loading = Loading();
} else {
_loading = loadingWidget;
}
if (errorWidget == null) {
_error = (Object error) => FutureErrorWidget(error: error);
} else {
_error = errorWidget;
}
if (emptyWidget == null) {
_empty = Center(child: Text('No data available.'));
} else {
_empty = emptyWidget;
}
}
@override
Widget build(BuildContext context) {
return StreamBuilder<T>(
initialData: initialData,
stream: stream,
builder: (_, AsyncSnapshot<T> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return _loading;
break;
case ConnectionState.active: // check if different behavior is needed for active and done
case ConnectionState.done:
// error state
if (snapshot.hasError) {
// todo more throughout error checking and specialized error widget
return _error(snapshot.error);
}
// data state
if (snapshot.hasData) {
T data = snapshot.data;
return child(data);
}
// empty state
return _empty;
case ConnectionState.none:
default:
print('E: Received Future [$stream] was null or else.');
return _error('Unknown error.');
}
},
);
}
}
The FirebaseAuthService
wraps the auth.FirebaseAuth.instance
. My stream is constructed as follows:
User _userFromFirebase(auth.User user) {
if (user == null) {
return null;
}
return User(
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoUrl: user.photoURL,
);
}
@override
Stream<User> get onAuthStateChanged => _firebaseAuth.authStateChanges().map(_userFromFirebase);
I currently provide all my services above the ViewAuthGuard
.
I wrapped my Material app with a ThemeProvider ChangeNotifier (in case that could be an issue).
My issue is that all widgets below the ViewAuthGuard
are rebuild and their state is reset. This occurs to me while developing. When a hot reload occurs, all the children are rebuild. The TabsScreenView
contains the initial navigation for my flutter app and always reset to index zero during development.
Question: How do I avoid the unnecessary reloads at this point?
What I tested so far:
- I wrapped my named route for
TabsScreenView
with FutureBuilder / StreamBuilder and set it as the default route (Route Guards in Flutter) - Listening to the stream in
didComponentUpdate
and pushing named routes on User change - The solution provided above
Please drop me a comment if you need more information, code, console prints or else to support me. Thank you!