Flutter: Looking up a deactivated widget's ancestor is unsafe - android

I know that there are already two posts regarding this question, but I could not solve my issue looking at them. Probably because in my case the issue is different.
The code is the following. I want to load some data from the database while showing a loading page. After the data is loaded, I initialize the provider with the loaded data and then I move into a different page.
This code does not need to be in a StatefulWidget, but I try to put it in a StatefulWidget to solve the issue, but without success.
class _InitDBDataState extends State<_InitDBData> {
#override
Widget build(BuildContext context) {
_fetchData(context);
return const Center(child: const CircularProgressIndicator());
}
Future<void> _fetchData(BuildContext context) async {
print('fetching data...');
print('context: $context');
final initData = await DBService.service.getInitialData();
print('Data fetched');
print('context: $context');
Provider.of<DataProvider>(context, listen: false).init(initData);
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
}
}
I do not have any error if the application runs from scratch, but when I am doing a "Hot Reload" I get the following error pretty often, and it is annoying since I need to restart the application for each small change in the code.
I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#46860)
I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#55124)
I/flutter ( 9596): Data fetched
I/flutter ( 9596): context: _InitDBData
E/flutter ( 9596): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9596): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 9596): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 9596): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure>
package:flutter/…/widgets/framework.dart:3508
E/flutter ( 9596): #1 Element._debugCheckStateIsActiveForAncestorLookup
package:flutter/…/widgets/framework.dart:3522
E/flutter ( 9596): #2 Element.getElementForInheritedWidgetOfExactType
package:flutter/…/widgets/framework.dart:3588
E/flutter ( 9596): #3 Provider.of
package:provider/src/provider.dart:221
E/flutter ( 9596): #4 _InitDBDataState._fetchData
package:productive_diary/initScreen.dart:46
E/flutter ( 9596): <asynchronous suspension>
E/flutter ( 9596): #5 _InitDBDataState.build
I don't know why "fetching data..." is printed twice, and I have no clue on how to solve the issue.
I thought the issue was solved with the solution of Saman Salehi, but working in debug mode I had the same exception in the _fetchData function, that now is called in the function initState()
Exception has occurred.
FlutterError (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 dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)
I got another error after applying the edits suggested by Stewie Griffin
.
The error is on the line Provider.of<DataProvider>(context, listen: false).init(initData);
I got it during a hot reload. It seems less common than the other error, so the answer of Stewie Griffin surely improved the stability of my Stewie Griffin
E/flutter (23815): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'owner' was called on null.
E/flutter (23815): Receiver: null
E/flutter (23815): Tried calling: owner
E/flutter (23815): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (23815): #1 Provider.of
package:provider/src/provider.dart:193
E/flutter (23815): #2 _InitDBDataState._fetchData
package:productive_diary/initScreen.dart:49
E/flutter (23815): <asynchronous suspension>
E/flutter (23815): #3 _InitDBDataState.initState
Could you please help me?

First of all, never call async methods inside of build as mentioned. Build method is constantly rebuilt and it will cause your fetch method to be repeated like an infinite loop. After you fix that, you still get the same error because of this part:
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
You shouldn't call Navigator during build. Here is what you need to do:
Add this line to the top of your file to use SchedulerBinding:
import 'package:flutter/scheduler.dart';
Wrap Navigator with SchedulerBinding to wait for completing the state before navigating to another screen. Then, call your async method inside of initState.
class _InitDBDataState extends State<_InitDBData> {
#override
void initState() {
// Call your async method here
_fetchData();
super.initState();
}
Future<void> _fetchData() async {
print('fetching data...');
print('context: $context');
final initData = await DBService.service.getInitialData();
print('Data fetched');
print('context: $context');
Provider.of<DataProvider>(context, listen: false).init(initData);
// Wrap Navigator with SchedulerBinding to wait for rendering state before navigating
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
});
}
#override
Widget build(BuildContext context) {
return Center(child: CircularProgressIndicator());
}
}
Tip: You don't need to pass the context in a Stateful Widget because you can access it from everywhere.

You shouldn't use the build method for anything other than building UI. build can be called at any time even when it's not on the screen.
I would move the _fetchData to the initState so it wouldn't cause any conflict at the build method.
class _InitDBDataState extends State<_InitDBData> {
#override
void initState() {
super.initState();
_fetchData(context);
}
#override
Widget build(BuildContext context) {
return const Center(child: const CircularProgressIndicator());
}
Future<void> _fetchData(BuildContext context) async {
print('fetching data...');
print('context: $context');
final initData = await DBService.service.getInitialData();
print('Data fetched');
print('context: $context');
Provider.of<DataProvider>(context, listen: false).init(initData);
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
}
}

Navigator.pushReplacement(context,MaterialPageRoute(builder:(context) => WelcomeScreen()),);
|^| .wrap this code SchedulerBinding.instance!.addPostFrameCallback on above code.
like this below:
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => WelcomeScreen()),
);
});
Schedule a callback for the end of this frame.
Does not request a new frame.
This callback is run during a frame, just after the persistent frame
callbacks (which is when the main rendering pipeline has been
flushed). If a frame is in progress and post-frame callbacks haven't
been executed yet, then the registered callback is still executed
during the frame. Otherwise, the registered callback is executed
during the next frame.
The callbacks are executed in the order in which they have been added.
Post-frame callbacks cannot be unregistered. They are called exactly
once.
Before:
After
SampleCode Here i use rive: ^0.8.1 dartpackage
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rive/rive.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(home: SimpleAnimation()));
}
setdata(BuildContext context) async {
await Future.delayed(const Duration(seconds: 5), () {
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => WelcomeScreen()),
);
});
});
}
class SimpleAnimation extends StatelessWidget {
const SimpleAnimation({Key? key, this.loading}) : super(key: key);
final bool? loading;
#override
Widget build(BuildContext context) {
setdata(context);
return Scaffold(
body: Center(
child: Container(
height: 200,
width: 200,
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
),
),
),
);
}
}
class WelcomeScreen extends StatelessWidget {
const WelcomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
child: Text(
"HOME PAGE",
style: TextStyle(fontSize: 50),
),
),
),
);
}
}

if it is caused by accessing ScaffoldMessenger by context then putting it inside a try catch will resolve context error.
try{
ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar();
} catch(e){
print(e);
}

For this probleme you can use one of this two solutions :
first: add scheduler like this :
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
});
second: add future delayed with some milliseconds like this :
Future.delayed(Duration(microseconds: 200))
.then((value) {
Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
});
!!! But if you a lot of change of state in the same time this solutions may still give the same error.
if is the case try to use the second solution by augmenting the duration.

Related

stateless Widget Keeps Rebuilding

I'M currently working on a to-do app following a tutorial
this is the checkbox part
I'm trying to lift it's state up the tree, but when I do so the stateless widget just keeps rebuilding non stop ,
I tried adding key , adding const converting it to stateful but nothing seems to work
it also doesn't provide me with any kind of error or exceptions messages
note : when I set the onChecked to null the statelessWidget runs twice when it should only build once..
below is the code:
import 'package:flutter/material.dart';
class TaskTile extends StatefulWidget {
#override
_TaskTileState createState() => _TaskTileState();
}
class _TaskTileState extends State {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
"Complete Todoey",
style: TextStyle(
decoration: isChecked == true
? TextDecoration.lineThrough
: TextDecoration.none),
),
trailing: TaskCheckBox(
checkBoxState: isChecked,
toggleCheckBoxState:
(bool checkBoxState) {
Future.delayed(Duration.zero,
() async {
setState(() {
print(
"===================$isChecked==============$checkBoxState======================");
isChecked = checkBoxState;
});
});
}),
);
}
}
class TaskCheckBox extends StatelessWidget {
const TaskCheckBox(
{Key? key,
required this.checkBoxState,
required this.toggleCheckBoxState})
: super(key: key);
final bool checkBoxState;
final Function toggleCheckBoxState;
#override
Widget build(BuildContext context) {
print(
"=====================================$checkBoxState+++++++++++++++++++++++++++++++++++++++++");
return Checkbox(
value: checkBoxState,
onChanged:
null, // toggleCheckBoxState(checkBoxState),
);
}
}
a snip from the console output :
Performing hot restart...
Syncing files to device Android SDK built for x86...
I/flutter ( 3401): ===================false==============false======================
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
I/flutter ( 3401): ===================false==============false======================
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
I/flutter ( 3401): ===================false==============false======================
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
I/flutter ( 3401): ===================false==============false======================
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
I/flutter ( 3401): ===================false==============false======================
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
a snip from the output when onChanged is set to null :
Restarted application in 2,694ms.
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
I/flutter ( 3401): =====================================false+++++++++++++++++++++++++++++++++++++++++
The purpose of setState is to tell the framework that a variable in the state has changed and the widget needs to be rebuilt to reflect that change. So calling setState calls the build function again, which in your case recalls your Future, which calls setState again, which triggers build and so on.
To fix this you could get rid of async function with Future.delayed since it's not needed there at all as well as putting print outside your setstate function.

cant get subcollection in firebase firestore

i cant get subcollection that i created before. i am able to create subcollection named "sinav_gorselleri" after i pressed this RaisedButton and going to SinavOlusturPage with this code:
RaisedButton(
color: Colors.blue,
child: Text("Sınav oluştur"),
onPressed: () async{
final newDoc = await FirebaseFirestore.instance.collection("ders_sinavlari_olusturulanlar")
.add({"baslik": "4oluşturulanSınav2", "gorsel": "gorsel", "konu": "", "ogretmen": "ömer kalfa",
"sira": 3, "tarih": ""});
final idnewDoc = newDoc.id;
debugPrint(idnewDoc);
final newDoc_newCol = await newDoc.collection("sinav_gorselleri")
.add({"gorsel": "https://firebasestorage.googleapis.com/v0/b/sbycpaldemo.appspot.com/o/ders_notlari_gorseller%2Fyeni?alt=media&token=4af59ada-4a8b-45cc-86ef-2f691a5baf62"});
final idnewCol = await newDoc_newCol.id;
debugPrint(idnewCol);
Navigator.of(context,rootNavigator: true).pop('dialog');
Navigator.push(context, MaterialPageRoute(builder: (context)=> SinavOlusturPage(idnewDoc: idnewDoc,)));
}),
and in SinavOlusturPage i am expecting to get first doc in subcollection named "sinav_gorselleri" but cant get it with this code:
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class SinavOlusturPage extends StatefulWidget{
final idnewDoc;
const SinavOlusturPage({Key key, this.idnewDoc}) : super(key: key);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return SinavOlusturPageState(this.idnewDoc);
}
}
class SinavOlusturPageState extends State {
final idnewDoc;
SinavOlusturPageState(this.idnewDoc);
File _imageSelected;
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(key: _scaffoldKey,
appBar: AppBar(
title: Text("SINAV OLUŞTURMA SAYFASI"),
),
body: ListView(
children: [
Center(
child: Text("..."),
StreamBuilder(
stream: FirebaseFirestore.instance.collection("ders_sinavlari_olusturulanlar/$idnewDoc/sinav_gorselleri").snapshots(),
builder: (context, snapshot){
final querySnapshot = snapshot.data();
return GridView.builder(
itemCount: 3,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 10, crossAxisCount: 2,),
itemBuilder: (context, index){
final mapOlusturulan = querySnapshot.docs[index].data();
final idOlusturulan = querySnapshot.docs[index].id;
return GridTile(
child: Center(
child: Image.network(mapOlusturulan["gorsel"])),
);
});
})
],
),
);
}
}
i did tried
FirebaseFirestore.instance.collection("ders_sinavlari_olusturulanlar").doc(idnewDoc) .collection("sinav_gorselleri").snapshots(), also but cant do it. here is my error that i get all the time:
Performing hot reload...
Syncing files to device SNE LX1...
════════ Exception caught by image resource service ════════════════════════════════════════════════
The following ArgumentError was thrown resolving an image codec:
Invalid argument(s): No host specified in URI file:///gorsel
When the exception was thrown, this was the stack:
#0 _HttpClient._openUrl (dart:_http/http_impl.dart:2407:9)
#1 _HttpClient.getUrl (dart:_http/http_impl.dart:2328:48)
#2 NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:89:59)
#3 NetworkImage.load (package:flutter/src/painting/_network_image_io.dart:50:14)
#4 ImageProvider.resolveStreamForKey.<anonymous closure> (package:flutter/src/painting/image_provider.dart:504:13)
...
Image provider: NetworkImage("gorsel", scale: 1.0)
Image key: NetworkImage("gorsel", scale: 1.0)
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The method 'call' was called on null.
Receiver: null
Tried calling: call()
The relevant error-causing widget was:
StreamBuilder<QuerySnapshot> file:///C:/ornekler/sby_cpal_demo/lib/Dersler/SinavOlusturPage.dart:39:9
════════════════════════════════════════════════════════════════════════════════════════════════════
Reloaded 22 of 694 libraries in 3.748ms.
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building StreamBuilder<QuerySnapshot>(dirty, state: _StreamBuilderBaseState<QuerySnapshot, AsyncSnapshot<QuerySnapshot>>#41144):
Class 'QuerySnapshot' has no instance method 'call'.
Receiver: Instance of 'QuerySnapshot'
Tried calling: call()
The relevant error-causing widget was:
StreamBuilder<QuerySnapshot> file:///C:/ornekler/sby_cpal_demo/lib/Dersler/SinavOlusturPage.dart:39:9
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 SinavOlusturPageState.build.<anonymous closure> (package:sby_cpal_demo/Dersler/SinavOlusturPage.dart:42:50)
#2 StreamBuilder.build (package:flutter/src/widgets/async.dart:525:81)
#3 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:129:48)
#4 StatefulElement.build (package:flutter/src/widgets/framework.dart:4744:28)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by image resource service ════════════════════════════════════════════════
Invalid argument(s): No host specified in URI file:///gorsel
════════════════════════════════════════════════════════════════════════════════════════════════════
"gorsel" is my unique field key of subcollection document. this error realy makes me tired but really need to use subcollections in my app.
i didnt solved this with codings i just removed all the codes, pages and stuffs recorded to firebase firestore and rewrite them all step by step. i guess i get the reason of the error. it was about navigation time. after i pressed the button named Sinav Oluştur i was expecting the creation of the subcollection named "soru_gorselleri" of new document firstly and then navigation to SinavOlusturPage but all of these were happennig reversely so the Page was returning null. after i did all of them step by step with different RisedButtons , all of errors gone and happy end.

Flutter: Dependency Injecting using Multiprovider and Consumer in the same tree

I'm trying to inject instances of services (that have been created in the same tree level) into another provider. But down the tree when accessing the provider, I get ProviderNotFoundException exception.
In the following code NotificationService depends on AuthService. Which needs to be passed in the constructor. Hence I inject it using Consumer and Provider.value as mentioned in the docs: https://pub.dev/documentation/provider/latest/provider/Consumer-class.html
Here is the pseudo-code:
return MultiProvider(
providers: [
Provider<AuthService>(
create: (ctx) => AuthService(_storage),
dispose: (ctx, v) => v.dispose(),
),
Consumer<AuthService>(
builder: (context, v, child) {
return Provider.value(
value: Provider<NotificationService>(
create: (ctx) => NotificationService(v),
dispose: (ctx, v) => v.dispose(),
),
child: child
);
},
)
],
child: MyApp()
);
Somewhere down the tree line, When trying to access the NotificationService instance, I get ProviderNotFoundException:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NotificationService _ns = Provider.of<NotificationService>(context);
}
}
Error:
I/flutter ( 4614): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4614): The following ProviderNotFoundException was thrown building MyApp(dirty, dependencies:
I/flutter ( 4614): [_DefaultInheritedProviderScope<AuthService>]):
I/flutter ( 4614): Error: Could not find the correct Provider<NotificationService> above this MyApp Widget
I/flutter ( 4614):
I/flutter ( 4614): To fix, please:
I/flutter ( 4614):
I/flutter ( 4614): * Ensure the Provider<NotificationService> is an ancestor to this MyApp Widget
I/flutter ( 4614): * Provide types to Provider<NotificationService>
I/flutter ( 4614): * Provide types to Consumer<NotificationService>
I/flutter ( 4614): * Provide types to Provider.of<NotificationService>()
I/flutter ( 4614): * Ensure the correct `context` is being used.
I/flutter ( 4614):
I don't fully understand this, and I'm pretty sure there is a mistake in the above code. What am I doing wrong?
The way you used Provider.value is invalid. But you don't actually need Consumer+Provider. You can do:
MultiProvider(
providers: [
Provider(create: (_) => A()),
Provider(create: (context) => B(Provider.of<A>(context, listen: false)),
],
)

flutter return data from a screen error with pushNamed()

I use pushNamed() to get return data from screen like this:
_pushToPage2() async {
String result = await Navigator.pushNamed(context, "/page2");
print("$tag $result");
}
But there was something wrong with the code, this the full error stack trace:
E/flutter: [ERROR:flutter/shell/common/shell.cc(181)] Dart Error: Unhandled exception:
type 'MaterialPageRoute' is not a subtype of type 'Route'
0 NavigatorState._routeNamed (package:flutter/src/widgets/navigator.dart:1408:14)
1 NavigatorState.pushNamed (package:flutter/src/widgets/navigator.dart:1454:20)
2 _MyHomePageState._pushToPage2 (file:///Users/wang/flutter_project/flutter_navigation/lib/main.dart:113:50)
3 _MyHomePageState.build. (file:///Users/wang/flutter_project/flutter_navigation/lib/main.dart:58:19)
4 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14)
5 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:562:30)
6 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
7 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9)
8 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:175:7)
9 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9)
10 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12)
11 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11)
12 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:143:19)
13 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
14 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
15 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
16 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48:7)
17 _invoke1 (dart:ui/hooks.dart:153:13)
18 _dispatchPointerDataPacket (dart:ui/hooks.dart:107:5)
But the following code is working properly:
_pushToPage2() async {
String result = await Navigator.push(
context, MaterialPageRoute(builder: (context) => Page2()));
print("$tag $result");
}
For me, the error was a different one (type 'MaterialPageRoute<dynamic>' is not a subtype of type 'Route<String>'), and I solved it by declaring the returning value as a dynamic type:
dynamic result = await Navigator.of(context).pushNamed("/page");
While this loses some type safety, it works fine for me.
I have a same problem. And finally I found a solution. Cast your result to your type.
For example:
String result = await Navigator.of(context).pushNamed("/some_route")
The code above will raise the exception.
But, you can do like this:
final result = await Navigator.of(context).pushNamed("/some_route") as String;
The code above will work as expected.
Just declare the working type Page2 for MaterialPageRoute in the router
case "/page2":
return MaterialPageRoute<Page2>(...};
None of the provided answers seem to address your issue as they advice you to abandon type safety by using dynamic or var. This is a bad workaround.
If you intent to use pushNamed while maintaining static typing, which you seem to want as you are providing the type String for the variable you want to store the result in, there is only one possibility: type casting.
So instead of using pushNamed() as a generic method like this:
_pushToPage2() async {
String result = await Navigator.pushNamed<String>(context, "/page2");
print("$tag $result");
}
you typecast the method result like this:
_pushToPage2() async {
String result = await Navigator.pushNamed(context, "/page2") as String;
print("$tag $result");
}
Not being able to provide a type to the pushNamed() method is a known deficiency of Navigator in combination with route tables, as stated here.
In your main.dart file and inside your main Function define MaterialApp widget, and inside it we define routes (screens) of our App.
main.dart:
void main() {
runApp(
new MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder> {
'/screen1': (BuildContext context) => new Screen1(),
'/screen2' : (BuildContext context) => new Screen2()
},
)
);
}
Screen1:
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
}
}
Screen2:
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
}
}
And to Navigate to screens we do:
Navigator.of(context).pushNamed('/screen2');
async and awiat is not needed but if you are doing async programming for e.g; you are querying Firestore and on the basis of the result of query, you choose to navigate to the new screen or choose to show the error.
For more details: routing and navigation in flutter
Edit:
To pass data between Screens, pass the argument to the Screen2 constructor and use Navigator.push(). Remember that in Navigator.pushNamed() you pass context and route name only, and you can't pass arguments in it.
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => new Screen2(new Color(0xFF66BB6A)),
));
Screen2:
class Screen2 extends StatelessWidget {
Screen2(this.color);
final Color color;
#override
Widget build(BuildContext context) {
...
}
}

Flutter Custom Keyboard ('Fake' soft keyboard)

I'm working on a finance app and I'd like a custom textentry field and keyboard for currency entry with an inbuilt calculator.
I've tried using a BottomSheet, both persistent and modal. The modal behaviour is ideal, but it always shows a barrier. The persistent one is what I have now, using a focus node to show and hide it, but it's throwing strange errors:
I/flutter (30319): The following NoSuchMethodError was thrown while dispatching notifications for FocusNode:
I/flutter (30319): The method 'removeLocalHistoryEntry' was called on null.
I/flutter (30319): Receiver: null
I/flutter (30319): Tried calling: removeLocalHistoryEntry(Instance of 'LocalHistoryEntry')
I/flutter (30319):
I/flutter (30319): When the exception was thrown, this was the stack:
I/flutter (30319): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:46:5)
I/flutter (30319): #1 LocalHistoryEntry.remove (package:flutter/src/widgets/routes.dart:296:12)
I/flutter (30319): #2 _NumpadFieldState.initState.<anonymous closure> (file:///D:/code/financepie/lib/widgets/numpad/numpadfield.dart:30:32)
...
In any case, the bottom sheet behaviour (dragging down) isn't really ideal to copy the android/ios soft keyboard. Any better solutions? Current code below:
import 'package:flutter/material.dart';
import 'numpad.dart';
class NumpadField extends StatefulWidget {
#override
_NumpadFieldState createState() {
return new _NumpadFieldState();
}
}
class _NumpadFieldState extends State<NumpadField> {
ValueNotifier<List<String>> state;
FocusNode focusNode;
PersistentBottomSheetController bottomSheetController;
#override initState() {
super.initState();
state = ValueNotifier<List<String>>([]);
state.addListener(() => setState((){}));
focusNode = FocusNode();
focusNode.addListener(() {
print(focusNode);
if (focusNode.hasFocus) {
bottomSheetController = showBottomSheet(
context: context,
builder: (context) => Numpad(state: state),
);
} else {
bottomSheetController?.close(); ///this line causing the error
}
});
}
#override dispose() {
state.dispose();
focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(focusNode);
},
child: Container(
child: Text(state.value.fold<String>("", (str, e) => "$str $e")),
constraints: BoxConstraints.expand(height: 24.0),
decoration: BoxDecoration(
border: BorderDirectional(bottom: BorderSide())
),
),
);
}
}
bottomSheetController?.close(); is nullable. Since the line causes the error, you can add a null-check to prevent this issue.
else {
if(bottomSheetController != null)
bottomSheetController!.close();
}

Categories

Resources