Flutter: Retrieving top-level state from child returns null
Asked Answered
R

2

2

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?

Rosales answered 5/11, 2017 at 12:12 Comment(4)
dumb question, it could be irrelevant but, when importing your files, are you using relative path ("./homepage.dart") or absolute path ("package:my_package/homepage.dart") ?Negus
I am using absolute pathing, so like package:my_package/etc/file.dartRosales
I had a similar bug a long time ago, I am not sure but using relative path solved it, or it was something else ^^Negus
This did indeed fix it, thanks a lot! I'm still awaiting a "proper" reply (as to what causes this, and why relative imports fix it), so I'm not marking the question as answered yet. But thanks anyways!Rosales
S
24

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

Skimp answered 6/11, 2017 at 16:59 Comment(4)
I used absolute import but this issue still happens. Any other clue?Caloyer
Actually I'm pretty sure this is not the case. Dart is capable of canonicalizing relative and absolute paths. There was a Flutter related issue fixed a few weeks ago that broke this canonicalization because the entry-point file is in lib/ instead of bin/ how it was originally designed in Dart. I have seen it mentioned that there can still be cases on Windows when uppercase letters are used in file names that break this, but I don't use Windows hand haven's experienced that.Douzepers
@GünterZöchbauer I'm certain this was true at some point. But this may have been fixed in the meantime, I haven't checked it yetUrias
While it was true it was only when relative imports were used lib/main.dart. If this file contained only package imports, then you could mix relative and package imports everywhere else.Douzepers
B
4

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

Breeches answered 7/4, 2020 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.