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
TabsScreenViewwith FutureBuilder / StreamBuilder and set it as the default route (Route Guards in Flutter) - Listening to the stream in
didComponentUpdateand 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!
I was able to fix it myself. For anyone interested, this was the process:
The
streamis the pivot element which decides if theAsyncStreamWidgetrebuilds its child or not. So I did check if thestreamchanged across hot reloads by printing itshashCodeattribute (and yes, it did change).Subsequently, I did change my
ViewAuthGuardto aStatefulWidgetand used thedidChangeDependenciesmethod to store thestreamin the state.(I also started instantiating the widgets in the
initStatemethod and store them in the state as well so that they are created only once.)EDIT And I should mention that my
ViewAuthGuardhas no dependencies. So it is not rebuilt due to a change of dependencies.