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