Diagnosing why Flutter MaterialApp theme runtime change is slow on device only - dart

I'm wrapping a SharedPreferences service class in a provider class that implements PreferencesProvider.of(context). It sets the theme as follows:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: PreferencesProvider.of(context).service.isDark,
initialData: PreferencesProvider.of(context).service.isDark.value, //its an rxDart ValueObservable
builder: (context, snapshot) => MaterialApp(
theme: ThemeData(
brightness: snapshot.data ? Brightness.dark : Brightness.light,
primarySwatch: Colors.blue,
///etc etc etc
Widgets can change the theme simply with PreferencesProvider.of(context).service.toggleIsDark(). It works well, but it takes about 3 seconds of being frozen to change theme on my device. On the emulator, it happens instantly.
Any possible leads on debugging this would be great. I've tried using the Dart Observatory Timeline but I couldn't see anything helpful (I couldn't make much sense of it).

This was happening because I was creating my BLoCs in the outermost build function. I moved creation to main() and passed in the created BLoCs which solved the problem. Don't create anything except widgets in build functions!

Related

Does an empty const constructor matter?

This is a follow-up question to
How does the const constructor actually work?,
So far from what I've read about const constructors in Dart, it ensures that only one object of the class in question is allocated. In theory, this can save allocation space and execution time. It's even recommended to apply it wherever possible when following Effective Dart design.
Now, say we have a stateless widget Foo:
import 'package:flutter/material.dart';
class Foo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Hello world");
}
}
Without defining an explicit const constructor, this widget cannot be used in a const context. In other words, the following snippet
import 'package:flutter/material.dart';
class Bar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const Foo();
}
}
is illegal. But is there even a benefit to adding a const Foo(); to the Foo class if it doesn't (and won't have) any fields in the foreseeable future?
Yes, there is a benefit. You should declare as const if possible.
According to the Flutter doc:
Use const widgets where possible, and provide a const constructor for the widget so that users of the widget can also do so.
The benefits are the same as having a default vs const constructor:
Performance (see)
Your code is consistent and sound
Are used as annotations
Can use in compile switch
You can enable const by default using Lint

Widget re-render when I focus on a TextField - Bloc Pattern

I'm using a BLoC to keep state between two nested FullScreenDialogs.
I'm initializing the bloc when I push the first screen, like so
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ProductBlocProvider(child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
ProductEntryScreen has a bunch of TextFields and a button than opens a new FullScreenDialog. This new Screen also has TextFields.
The problem I'm having is that every time I write on a TextField on the second FullScreenDialog, the onPressed function where I start the ProductBlocProvider runs again.
And that re-run is causing the Bloc to create a new instance, so I end up loosing the state.
What I want to do?
Maybe I'm doing it wrong so I'll explain what I'm trying to achieve.
I want to keep state between the two FullScreenDialogs while I fill all the fields, and when I'm done I want to press a button that send all of the data (both screens) to a database.
The problem is that I was creating the instance of the bloc inside the provider in the builder function of the MaterialPageRoute.
That builder function was being called repeatedly, and creating a new instance of the bloc every time. The solution was to take out from the builde function the creation of the bloc instance, like this:
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//Here I create the instance
var _bloc = ProductBloc();
Navigator.of(context).push(MaterialPageRoute(
//And I pass the bloc instance to the provider
builder: (BuildContext context) => ProductBlocProvider(bloc: _bloc, child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
The package get_it may be of help to you. get_it is a service locator library, and uses a Map to store the registered objects; therefore, it provides access at a complexity of O(1), which means it's incredibly fast. The package comes with a singleton GetIt which you can use like so,
// Create a global variable (traditionally called sl or locator)
final sl = GetIt.instance; // There is also a shorthand GetIt.i
// ...
// Then, maybe in a global function called initDi(),
// you could register your dependencies.
sl.registerLazySingleton(() => ProductBloc());
registerLazySingleton() or registerSingleton() will always
return the same instance; lazily (i.e., when first called)
or at app start-up respectively.
If you want to create a new instance every time, use registerFactory() instead (I put this here even though it's not exactly what you want).
For example,
sl.registerFactory(() => ValidatorCubit());
And it could be accessed like this,
MultiBlocProvider(
providers: [
// The type is inferred here
BlocProvider<AuthenticationBloc>(create: (_) => sl()),
// The type is explicitly given here
BlocProvider(create: (_) => sl<ProductsBloc>()),
],
child: ProductsScreen(),
),
This example primarily shows you how it can be done with the flutter_bloc library, but get_it works anywhere, even in non-flutter dart projects.
If you need more functionality, do make sure to read the docs for this package. It is well documented, and contains (almost) every feature you might need, including scoping.
Also, this approach allows you to use the interface pattern, making the code much more maintainable and testable, as you will have to change just one place to use a different implementation.

Navigator error in flutter (Drawer button logout)

I have a Drawer that i share in all my StatefulWidget like this
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
drawer: SharedDrawer()
... //More code
In the Drawer i put a LogOut button that redirect the user to the login page cleaning all the router stack like this.
Navigator.of(context).pop();
Navigator.of(context).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
but when i try to log in again to the app this error appears.
flutter: Looking up a deactivated widget's ancestor is unsafe. At this
point the state of the widget's element tree is no longer stable. To
safely refer to a widget's ancestor in its dispose() method, save a
reference to the ancestor by calling inheritFromWidgetOfExactType() in
the widget's didChangeDependencies() method.
what i'm doing wrong ?
how i can manage the login/out in the app or is something with the navigator stack ?
Regards!
The error "Looking up a deactivated widget's ancestor is unsafe" is usually caused by reusing reference of Widgets that has been disposed previously i.e. during Navigator.of(context).pop();
One way to solve this issue is to keep track the context that you're using on your widgets so it won't reuse previoulsy disposed contexts.

Flutter: Retrieving top-level state from child returns null

I'm trying to obtain the top-level state of my app using a .of()-method, similar to the Scaffold.of() function. This is the (stripped down) code:
class IApp extends StatefulWidget {
#override
IAppState createState() => new IAppState();
static IAppState of(BuildContext context) =>
context.ancestorStateOfType(const TypeMatcher<IAppState>());
}
The app is started using runApp(new IApp)
This Widget creates a HomePage:
#override
Widget build(BuildContext context) {
return new MaterialApp(
// ommitted: some localization and theming details
home: new HomePage(),
);
}
Then, I try to access the State from the HomePage (a StatefulWidget itself):
#override
Widget build(BuildContext context) {
return new Scaffold(
// ommited: some Scaffold properties such as AppBar
// runtimeType not actual goal, but just for demonstration purposes
body: new Text(IApp.of(context).runtimeType.toString()),
);
}
The strange this is, the code works when I place the code for HomePage in the same file as the IApp, but just as an extra class. However, when I place HomePage in a separate file (main.dart and homepage.dart importing each other), the return value of IApp.of(context) is null.
What causes this? And how can I fix it?
TDLR: imports file only using
import 'package:myApp/path/myFile.dart';
Never with
import './myFile.dart';
This is due to how dart resolves imports.
You may have a single source file, but during builds, there is some kind of duplicates.
Let's say you're working on 'myApp'. To import a file, you could do both :
import 'relativePath/myFile.dart'
import 'package:myApp/path2/myFile.dart'
You'd think that they point to the same file right?
But no. One of them will point to the original source. While the other one will point to a temporary file used for the build.
The problem comes when you start to mix both solutions. Because for the compiler, these two files are different. Which means that IApp imported from package:myApp/IApp is not equal to the same IApp imported from relativePath/myApp/IApp
In your case, you inserted in your widget tree an IApp from pakage:path but your IApp.of(context) use IAppState resolved locally.
They both have a different runtimeType. Therefore const TypeMatcher<IAppState>() won't match. And your function will return null.
There's an extremely easy way to test this behavior.
Create a test.dart file containing only
class Test {
}
then in your main.dart add the following imports :
import 'package:myApp/test.dart' as Absolute;
import './test.dart' as Relative;
You can finally test this by doing :
new Relative.Test().runtimeType == new Absolute.Test().runtimeType
Spoiler: the result is false
Now you can use the relative path.
You can verify this, as Remy suggested two years ago:
Relative.Test().runtimeType == Absolute.Test().runtimeType
Spoiler: the result is true

Observer for Navigator route changes in Flutter

Is there a way to listen to route changes of the Navigator in Flutter? Basically, I'd like to be notified when a route is pushed or popped, and have the current route on display on screen be returned from the notification
Building on navigator observers, you can also use RouteObserver and RouteAware.
Navigator has observers. You can implement NavigatorObserver and receive notifications with details.
I was also struggling with that and for my purpose the RouteObservers felt overkill and where too much for my needs. The way I handled it lately was to use the onGenerateRoute property inside my MaterialApp. So this solution is more applicable if you are using onGenerateRoute with your app (might especially be useful if you are navigating with arguments). You can read more about it here.
My MaterialApp looks like the following:
runApp(MaterialApp(
title: 'bla',
home: BuilderPage(LoginArguments(false)),
onGenerateRoute: generateRoute
));
The generateRoute method looks like the following:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'main':
print('Navigated to main')
return MaterialPageRoute(builder: (_) => MainScreen());
case 'register':
print('Navigated to register')
return MaterialPageRoute(builder: (_) => RegisterScreen());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
So everytime I now do for example:
Navigator.pushNamed(
context,
'register',
);
It will print Navigated to register. I think this way is especially helpful if you don't necessarily need to know whether you pushed or popped. With some additional implementation it would also be possible to observe that via injected arguments.

Resources