Flutter navigating MaterialApp children - flutter

I'm fairly new to flutter and dart, I set up my app like so:
class MyExpensesApp extends StatefulWidget {
const MyExpensesApp({Key? key}) : super(key: key);
#override
State<MyExpensesApp> createState() => _MyExpensesAppState();
}
class _MyExpensesAppState extends State<MyExpensesApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'title',
home: Directionality(
textDirection: TextDirection.rtl,
child: HomePage(),
),
);
}
}
and i have a FloatingAction button inside the HomePage which navigates to a different page when clicked
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
....
),
floatingActionButton: FloatingActionButton(
onPressed: () => {
Navigator.push(
context, MaterialPageRoute(builder: (context) => ExpensePage()))
},
tooltip: "add expense",
child: Icon(Icons.add),
),
);
}
}
but when navigation to the ExpensePage screen, it is not RTL, which means it is not a child of Directionality widget?
Am i just replacing the whole MaterialApp with ExpensePage?
How can i only replace the Directionality Child when navigating?
I don't need any intricate routing, as i only have those 2 pages.

To put a Directionality widget above the Navigator (and hence above every page), you can use the builder property in MaterialApp.
return MaterialApp(
title: 'title',
home: HomePage(),
builder: (context, child) => Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);

For the child page, you can use Scaffold as the root parent.
Then try with the same routing.

Related

Flutter align AppBar back button to right side

I am using textDirection: TextDirection.rtl on the MaterialApp:
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: appTitle,
home: Directionality(
textDirection: TextDirection.rtl,
child: MyHomePage(title: appTitle),
),
);
Everything is aligned to right and works perfectly, except for the back button of the AppBar.
When you navigate to other pages (specifically I'm using drawer) the back button is aligned to the left and not to the right.
Navigation code:
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => const SettingsView(),
),
);
"Settings" page:
return Scaffold(
appBar: AppBar(title: Text("Settings"))
);
As you can see I didn't touch the leading property, I thought it should be automatically...
I am using builder to handle this.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
builder: (context, child) => Directionality(
textDirection: TextDirection.rtl,
child: child ?? const SizedBox.shrink(),
),
);
}
}
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
#override
Widget build(BuildContext context) {
print(Directionality.of(context).toString());
return Scaffold(
appBar: AppBar(
title: Text("Settings"),
),
);
}
}

how can i fix : Navigator operation requested with a context that does not include a Navigator

I´m new in flutter and i´m trying to create a navigation to another page called Registro();
I'm trying with this solutionthat i found in a post here in stackoverflow.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(home: Login());
}
}
class Login extends StatelessWidget {
const Login({Key? key}) : super(key: key);
static const String _title = 'Sample App';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
bottomNavigationBar: BottomAppBar(
elevation: 3,
child: Row(
children: <Widget>[
const Text('¿No tienes una cuenta?'),
TextButton(
child: const Text(
'Registrate',
style: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => new Registro()),
);
},
)
],
mainAxisAlignment: MainAxisAlignment.center,
),
),
),
);
}
}
Also i tried this solution -> and doesnt works :c
Navigator operation requested with a context that does not include a Navigator
If you want to use Navigator.push(context, yourPageRoute), (which does a Navigator.of(context)), context needs to be able to access a Navigator placed above in the widget tree.
Usually, this Navigator is the root one that is being built in MaterialApp.
I your case you have
Widget build(BuildContext context) {
return MaterialApp( // <- The Navigator is inside this widget.
// ...
TextButton(
onPressed: () {
Navigator.of(context);
// ...
},
),
);
}
But the context you are using comes from the build method of MyApp, which is not below the MaterialApp widget you are using.
So you need to use the context that comes from below the MaterialApp in the widget tree.
How to solve it
You could use a Builder widget to be able to access the context from its builder method:
Widget build(BuildContext context) {
return MaterialApp( // <- The Navigator is inside this widget.
// ...
Builder(
builder: (context) {
return TextButton(
onPressed: () {
Navigator.of(context); // <- Now the context comes from Builder which is below MaterialApp and you should be able to access its Navigator.
// ...
},
),
},
),
);
}

Pass data to multiple screens with inheritedwidget

There are some confusions about InheritedWidget that I don't understand.
I have searched and read some QAs about InheritedWidget on stackoverflow, but there are still things that I don't understand.
First of all, let's create a scenario.
This is my InheritedWidget:
class MyInheritedWidget extends InheritedWidget {
final String name;
MyInheritedWidget({
#required this.name,
#required Widget child,
Key key,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) =>
oldWidget.name != this.name;
static MyInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
and this is MyHomePage that contains the MyInheritedWidget. MyInheritedWidget has two children: WidgetA and a button that navigates to another screen, in this case Page1.
class MyHomePage extends StatefulWidget {
#override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: MyInheritedWidget(
name: 'Name',
child: Column(
children: [
WidgetA(),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page1(),
),
);
},
child: Text('Go to page 1'),
)
],
),
),
),
);
}
}
Inside WidgetA there is a text widget that displays the name field from MyInheritedWidget and another button that navigates to Page2.
class WidgetA extends StatefulWidget {
#override
_WidgetAState createState() => _WidgetAState();
}
class _WidgetAState extends State<WidgetA> {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Column(
children: [
Text(myInheritedWidget.name),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page2(),
),
);
},
child: Text('Go to page 2'),
)
],
);
}
}
Page1 and Page2 each has only a text widget that displays the name field from MyInheritedWidget.
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Page 1'),
),
body: Text(myInheritedWidget.name),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
),
body: Text(myInheritedWidget.name),
);
}
}
In this scenario, the name field of MyInheritedWidget is not accessible form Page1 and Page2, but it can be accessed in WidgetA.
Now lets get to the question:
It is said that an InheritedWidget can be accessed from all of its descendants. What does descendant mean?
In MyHomePage, I know WidgetA is a descendant of MyInheritedWidget. but, is Page1 also a descendant of MyInheritedWidget?
If the answer is no, How can I make Page1 a descendant of MyInheritedWidget?
Do I need to wrap it again inside MyInheritedWidget?
What if there is a chain of navigations like this: Page1-> Page2 -> Page3 ... Page10 and I want to access MyInheritedWidget in Page10, Do I have to wrap each of the pages inside MyInheritedWidget?
As #pskink says, MyHomePage pushes Page1, which is a descendant of Navigator, which is under MaterialApp, not MyInheritedWidget. The easiest solution is to create MyInheritedWidget above MaterialApp. This is my code (using ChangeNotifierProvider instead of MyInheritedWidget).
void main() {
setupLocator();
runApp(DevConnectorApp());
}
class DevConnectorApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final log = getLogger('DevConnectorApp');
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>(
create: (ctx) => AuthService(),
),
ChangeNotifierProxyProvider<AuthService, ProfileService>(
create: (ctx) => ProfileService(),
update: (ctx, authService, profileService) =>
profileService..updateAuth(authService),
),
],
child: Consumer<AuthService>(builder: (ctx, authService, _) {
log.v('building MaterialApp with isAuth=${authService.isAuth}');
return MaterialApp(
Here is an example using multiple Navigators to scope the InheritedWidget. The widget ContextWidget creates an InheritedWidget and a Navigator and has child widgets for the screens in your example.
class InheritedWidgetTest extends StatefulWidget {
#override
State createState() => new InheritedWidgetTestState();
}
class InheritedWidgetTestState extends State<InheritedWidgetTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
children: [
ContextWidget('First'),
ContextWidget('Second'),
],
),
),
);
}
}
class ContextWidget extends StatelessWidget {
Navigator _getNavigator(BuildContext context, Widget child) {
return new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(builder: (context) {
return child;
});
},
);
}
final name;
ContextWidget(this.name);
#override
Widget build(BuildContext context) {
return MyInheritedWidget(
name: this.name,
child: Expanded(
child: _getNavigator(
context,
PageWidget(),
),
),
);
}
}
class PageWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
WidgetA(),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page1(),
),
);
},
child: Text('Go to page 1'),
)
],
);
}
}

InheritedWidget becomes null on Navigator route change

I'm using an InheritedWidget to keep track of auth state. It works fine on main app. But as soon as I go to another page via a route change, the components in the new route cannot find the inherited widget.
Ideally, I would like all pages/routes to share the same auth state.
The following minimal example gives a NULL pointer error.
import 'package:flutter/material.dart';
void main() => runApp(EntryPoint());
class TestContext extends InheritedWidget {
const TestContext({
Key key,
Widget child,
}) : super(key: key, child: child);
final String hello = "foobar";
static TestContext of(BuildContext context) {
return context.inheritFromWidgetOfExactType(TestContext) as TestContext;
}
#override
bool updateShouldNotify(TestContext oldWidget) => true;
}
class EntryPoint extends StatelessWidget {
/// Needed for dialog
final navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'TODO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TestContext(
child: Scaffold(
body: Center(
child: Builder(
builder: (context) => FlatButton(
child: Text('Launch'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginForm()),
);
}))))));
}
}
class LoginForm extends StatefulWidget {
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
body: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter your JIRA website URL'),
keyboardType: TextInputType.url,
textInputAction: TextInputAction.next,
style: TextStyle(
fontSize: 28,
initialValue: TestContext.of(context).hello,
),
],
)),
));
}
}
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building LoginForm(dirty, state: LoginFormState#be3a1):
The getter 'hello' was called on null.
Receiver: null
Tried calling: hello
User-created ancestor of the error-causing widget was
MaterialApp
package:flutter_greeting_screen/main.dart:63
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 LoginFormState.build
package:flutter_greeting_screen/main.dart:144
When you use Navigator.of(context).push(...) or Navigator.push(context, ...) or Navigator.of(context).pushNamed(...) or Navigator.pushNamed(context, ...), the widget pushed is not a child of the widget that call Navigator.push (and its variants), this widget is a child of the closest instance of Navigator that encloses the given context, in your case the Navigator is created by the MaterialApp, so if you want to provide TestContext to all routes, the InheritedWidget must be a parent of the Navigator, in your case must be a parent of MaterialApp.
class EntryPoint extends StatelessWidget {
/// Needed for dialog
final navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return TestContext(
child: MaterialApp(
navigatorKey: navigatorKey,
title: 'TODO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Center(
child: Builder(
builder: (context) => FlatButton(
child: Text('Launch'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginForm()),
);
}))))),
);
}
}
I also recommend that you look at Provider widget of the provider package, which is a widget with syntax sugar for InheritedWidget.
A generic implementation of InheritedWidget. It allows to expose any kind of object, without having to manually write an InheritedWidget ourselves.
Provider<String>.value(
value: 'Hello World',
child: MaterialApp(
home: Home(),
)
)
Provider<Auth>(
builder: (context) => Auth(),
dispose: (context, auth) => auth.dispose(),
child: MaterialApp(
home: Home(),
)
)

Is there any question about the route code in my flutter code?

I want to make a new route in the flutter , but I failed,
my VS Code give me this:
The following assertion was thrown while handling a gesture:
I/flutter (32582): Navigator operation requested with a context that does not include a Navigator.
I/flutter (32582): The context used to push or pop routes from the Navigator must be that of a widget that is a
I/flutter (32582): descendant of a Navigator widget
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Lake',
routes: {
'sss': (context)=>new NewRoute()
},
home: new Scaffold(
appBar: AppBar(
title: Text('Lake'),
),
body: Text('BBB'),
floatingActionButton: new FloatingActionButton(
child: Icon(Icons.import_contacts),
onPressed: (){
Navigator.pushNamed(context, 'sss');
},
),
),
);
}
}
class NewRoute extends StatelessWidget{
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: AppBar(
title: Text('BBB'),
),
body: Center(
child: Text('wahaha'),
),
);
}
}
Plea use this code
home: Builder(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text('Lake'),
),
body: Text('BBB'),
floatingActionButton: new FloatingActionButton(
child: Icon(Icons.import_contacts),
onPressed: (){
Navigator.pushNamed(context, 'sss');
},
),
),)
Builder let you build a new context from direct parent like described there https://docs.flutter.io/flutter/widgets/Builder-class.html
You are using routes incorrectly. when you use home in MaterialApp it will bypass the Routes. insted of that you can use initialRoute to define Home Screen
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Lake',
routes: {
'sss': (context) => const NewRoute(),
'home': (context) => const HomeScreen(),
},
initialRoute: 'home',
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lake'),
),
body: const Text('BBB'),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.import_contacts),
onPressed: () {
Navigator.pushNamed(context, 'sss');
},
),
);
}
}
class NewRoute extends StatelessWidget {
const NewRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BBB'),
),
body: const Center(
child: Text('wahaha'),
),
);
}
}
also avoid using new keyword.
you can also try with onGenarated Routes in flutter.
more information