Turning Flutter Arguments into Usable Variables on another dart file - flutter

My final goal is to have a userName variable from one dart file transferred to the other dart file.
Firebase Authentication will only take the email and password inputs so I need to pass the userName variable as an argument into another file that is called after the users email has been verified.
I have been trying to find videos and documentation online, most of what I found is trying to put the data into a list (which I would like to avoid). I don't understand the "this." getter function in flutter yet, I don't know if it's necessary to solve this problem. Let me know if there's anything I can clarify, I hope I'm overlooking something simple.
Dart File #1
onPressed: () => signUp(_email, _password).then((_) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => Verify(_userName)));
}),
Dart File #2
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class Verify extends StatefulWidget {
const Verify(String _userName, {Key? key}) : super(key: key);
#override
_VerifyState createState() => _VerifyState();
}
class _VerifyState extends State<Verify> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Text('Here is your variable $_userName please verify'),
),
);
}

I guess you're asking about passing arguments(any object) between different screens.
You can do this easily by passing it in RouteSettings, you can pass any object (String, int, map) and then fetch it in the build method of another Screen.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => Navigator.push(
context, MaterialPageRoute(
builder: (context) => HomeScreen(),
settings: const RouteSettings(arguments: 'username')),), //arguments
child: Text('Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as String; //arguments
return TextButton(
onPressed: () {},
child: Text(args,
style: Theme.of(context).textTheme.headline4,
),
);
}
}

Related

Is there a neat way to pop a page and schedule a callback to be called after navigation is complete?

I want to call Navigator.of(context).pop() one or several times and then run a callback after navigation has completed, but I have struggled to find a neat solution. I've put together an example app to illustrate the problem I'm having:
Screens A, B, and C all access a nullable value on the Model Provider
ScreenA can set value to a non-null value
ScreenB requires value to be non-null to build
ScreenC can set value to null and pop you back to ScreenA
When you press the button on ScreenC to go back to ScreenA, it navigates successfully (the app doesn't crash) but you throw an Error because it tries to build ScreenB after the first pop.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => Model(),
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ScreenA(),
),
);
}
}
class Model extends ChangeNotifier {
int? value = 0;
Future<void> updateValue(int? newValue) async {
await Future.delayed(const Duration(milliseconds: 30));
value = newValue;
notifyListeners();
}
}
class ScreenA extends StatelessWidget {
const ScreenA({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
Text('ScreenA - value: ${context.watch<Model>().value}'),
ElevatedButton(
child: const Text('Set value'),
onPressed: () => context.read<Model>().updateValue(Random().nextInt(100)),
),
ElevatedButton(
child: const Text('Go to B'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => ScreenB(
nonNullValue: context.watch<Model>().value ?? (throw Error()),
),
),
),
),
],
),
);
}
}
class ScreenB extends StatelessWidget {
const ScreenB({Key? key, required this.nonNullValue}) : super(key: key);
final int nonNullValue;
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
Text('ScreenB - value: $nonNullValue'),
ElevatedButton(
child: const Text('Set value'),
onPressed: () => context.read<Model>().updateValue(Random().nextInt(100)),
),
ElevatedButton(
child: const Text('Go to C'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const ScreenC(),
),
),
),
],
),
);
}
}
class ScreenC extends StatelessWidget {
const ScreenC({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
const Spacer(),
const Text('ScreenC'),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
context.read<Model>().updateValue(null);
},
child: const Text('Reset app')),
const Spacer(),
],
),
);
}
}
Widget centredScreenContent(List<Widget> widgets) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: widgets,
),
);
I've found two solutions, but neither feels neat:
Make ScreenB take a nullable value in its constructor, and have its build return something like value == null ? Container() : ActualContents(nonNullValue: value!). I don't like this though. If we know that in BAU use, ScreenB cannot be built while value == null, then we'd like to log an error if that happens in production so we can investigate the problem. We can't do this if our navigation back from ScreenC also hits this state though.
Add a sufficiently long delay to the callback so that it runs after the navigation is completed, e.g. in the example app, if you change Model.updateValue to have a 300ms delay, then it doesn't error. This also feels like an unpleasant solution, if the delay is too long we risk the app behaving sluggishly, if it's too short then we don't solve the problem at all.
I would make ScreenB(int? nullableParam) and handle the widget builder with additional assert nullableParam == null just to log the error.
But what i think the real solution you are looking for is context.read<Model>().value instead of watch - i can't think of a scenario where you want to page parameter depend on any listenable state
solution
ElevatedButton(
child: const Text('Go to B'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => ScreenB(
nonNullValue: context.read<Model>().value ?? (throw Error()),
This way the page after first build will not be rebuild with null when popped.
#Edit
I see 2 problems:
passing Listenable value to Page parameter
purposely setting value to null where other part of application purposely is not handling it
The first one can be solved with the solution above
The second you have to either assure the passed value will not be null on Navigator.pop() - the solution above does that. Or handle the null value in the ScreenB widget (as you suggested with conditional build)

Flutter&GetX: The first dialog not working after open the second dialog in the new page when using the Get.toNamed()

This issue is related with github #2502.
I am using GetMaterialApp from this package.
I'm not sure if this is a bug or not.
How to make the function in the first dialog useable by using Get.toNamed()?
It happened when using the Get.toNamed().
It works fine with Navigator.push() but I need Get.toNamed for the web app.
The first page has a button that will show the first dialog.
The first dialog will show the order type button list.
When pressing an order type button, the program will find a new order of this type and open the second page with a new order data.
The second page has some work to do and this work will open the second dialog.
After finishing this work, the user will click on the back button back to the first page and find a new order again.
The problem is when the second dialog works on the second page.
The first dialog on the first page will not work.
see video example.
web example.
code example:
import 'package:flutter/material.dart';
import 'package:flutter_test_exam_bug/config/path/page_path.dart';
import 'package:get/get.dart';
Future<void> _showMyDialog({required BuildContext context, required Widget child}) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) => child,
);
}
class PageTest extends StatefulWidget {
const PageTest({Key? key}) : super(key: key);
#override
_PageTestState createState() => _PageTestState();
}
class _PageTestState extends State<PageTest> {
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(onPressed: () => Get.toNamed(PagePath.test2), child: const Text("Open second page"))),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_), child: const Text("Open first dialog"));
return Scaffold(body: SafeArea(child: Center(child: openDialogButton_)));
}
}
class PageTest2 extends StatefulWidget {
const PageTest2({Key? key}) : super(key: key);
#override
State<PageTest2> createState() => _PageTest2State();
}
class _PageTest2State extends State<PageTest2> {
ButtonStyle buttonStyle = ElevatedButton.styleFrom(primary: Colors.green);
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context), child: const Text("I am second dialog"), style: buttonStyle)),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_),
child: const Text("Open second dialog"),
style: buttonStyle);
return Scaffold(appBar: AppBar(), body: SafeArea(child: Center(child: openDialogButton_)));
}
}
I think it is a bug.
When opening a dialog, the GETX ROUTE will change to the current page again.
Follow this in https://github.com/jonataslaw/getx/issues/2502

Flutter - Rendering cookies dialog conditionally

I am a beginner in Flutter & Dart language and trying to figure out how to render cookie settings dialog(popup) conditionally(based on user preference) on page load. I already found some 3rd party package (sharedpreferences) to store key-value pair for the user preferences. What I want to do is to check for user preference and if not found or false (Consent not given by clicking on Deny) this popup will just keep appearing on all pages. I also want users to be able to open this cookie settings popup by clicking on a link. How can I achieve that?
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: CookiesWidget(),
),
),
);
}
}
class CookiesWidget extends StatelessWidget {
const CookiesWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Cookie Settings'),
content: const Text('This website uses cookies. Please click OK to accept.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Deny'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
),
child: const Text('Show Cookie Settings'),
);
}
}
SharedPrefences + Visibility Widget would help you hide and show your cookie widget conditionally.
If you want users to open something within your app using a link, you should consider searching about deep-links.
I can't give a specific solution but resources to achieve most common cases:
A package for deep-link: uni_links
An article https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283
Official Flutter guide about deep links
Shared Preferences should be the best option.
You cloud use a FutureBuilder to render (or not render) the dialog based on the Shared Preferences' data.
You should create an async function inside your class but outside your build() method:
Future<bool> cookiesAccepted() async {
var prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('cookies')) {
bool? info = prefs.getBool('cookies');
return info ?? false;
}
return false;
}
And put this inside your build() method.
FutureBuilder<bool>(
future: cookiesAccepted(),
builder: (BuildContext context, AsyncSnapshot<bool> response) {
if (response.hasData) {
if (!response.data!) { //If the cookies were not accepted
return Text("Your cookies dialog");
}
}
},
),
Remember to store data inside SharedPreferences with prefs.setBool('cookies', value);

Flutter Errors: Undefined name "context"

I receive some errors when I try to push my flutter page from neighbourhoodList to individual neighbourhoods (e.g. neighbourhoodlist_admirality).
In my neighbourhoodlist, I would like to navigate to the individual neighbourhood pages when the user has clicked on the relevant neighbourhood. As I have not build the individual neighbourhood pages yet, I have linked them to an example page i.e. NeighbourhoodAdmirality.
This is my code for the neighbourhoodlist page:
import 'package:flutter/material.dart';
import 'indv_neighbourhoods/neighbourhoodlist_admirality.dart';
class NeighbourhoodList extends StatefulWidget {
NeighbourhoodList({ this.name = "name"});
final String name;
#override
_NeighbourhoodListState createState() => _NeighbourhoodListState();
}
class _NeighbourhoodListState extends State<NeighbourhoodList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Neighbourhoods',
),
),
body: _buildListView(context),
);
}
ListView _buildListView(BuildContext context){
return ListView.builder(
itemCount: allNeighbourhoods.length,
itemBuilder: (BuildContext content, int index) {
NeighbourhoodList neighbourhoodlist = allNeighbourhoods[index];
return NeighbourhoodListTile(neighbourhoodlist);
});
}
}
class NeighbourhoodListTile extends ListTile {
NeighbourhoodListTile(NeighbourhoodList neighbourhoodlist)
: super(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NeighbourhoodAdmiralty(neigh1)),
);
}
);
}
List<NeighbourhoodList> allNeighbourhoods = [
NeighbourhoodList(name: 'Admiralty'),
NeighbourhoodList(name: 'Aljunied'),
NeighbourhoodList(name: 'Ang Mo Kio'),
];
This is my code for an example page that I want to direct my neighbourhoodlist to when each individual neighbourhood is directed.
import 'package:flutter/material.dart';
class NeighbourhoodAdmiralty extends StatefulWidget {
final String neigh1;
NeighbourhoodAdmiralty(this.neigh1);
#override
_NeighbourhoodAdmiraltyState createState() => _NeighbourhoodAdmiraltyState();
}
class _NeighbourhoodAdmiraltyState extends State<NeighbourhoodAdmiralty> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Admiralty"),
),
body: Center(child: Text('This is the individual neighbourhood page'),
),
);
}
}
As I am still a beginner, I am facing some errors and have a few questions on these:
Error 1 on neighbourhoodlist.dart: "Undefined name 'context'" under Navigator.push --> not sure why this happens as I have already passed the BuildContext in my methods above
Error 2 on neighbourhoodlist.dart: "Undefined name 'neigh1'" under Navigator.push --> I would like to redirect the neighbourhoodlist.dart page to the individual neighbourhood sheets but I'm not sure what I pass here, I have tried 'neigh1' (my variable in neighbourhoodlist_admirality), 'name' - the variable in NeighbourhoodList, and 'index' 0 the variable inNeighbourhoodListState but none of them seem to work so far.
Appreciate all your help in resolving this and thanks in advance !
When you do
class NeighbourhoodListTile extends ListTile {
NeighbourhoodListTile(NeighbourhoodList neighbourhoodlist)
: super(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => NeighbourhoodAdmiralty(neigh1)));
}
);
}
you can't access context in onTap because Flutter hasn't yet provided one to you in this point. If you need the context, use a StatelessWidget instead, where you can access it in the build method:
class NeighbourhoodListTile extends StatelessWidget {
final NeighbourhoodList neighbourhoodlist;
const NeighbourhoodListTile(this.neighbourhoodlist);
#override
Widget build(BuildContext context) {
// Here's your context ^^^^^^^
return ListTile(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => NeighbourhoodAdmiralty(neigh1)));
}
);
}
}
As to your second error, there is also no neigh1 variable at this point. I don't know what your logic is, but I think you want to replace it with neighbourhoodlist.name:
class NeighbourhoodListTile extends StatelessWidget {
final NeighbourhoodList neighbourhoodlist;
const NeighbourhoodListTile(this.neighbourhoodlist);
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => NeighbourhoodAdmiralty(neighbourhoodlist.name)));
}
);
}
}

Undefined named 'context' while navigating

import 'package:flutter/material.dart';
import 'fruits_listing_card.dart';
import 'fruits_page.dart';
Map<String, Widget> fruits = {
"banana": FruitsListingCards(
fruitBGColor: 0xFFF8A8B5,
fruitImagePath: 'images/fruits/banana.png',
fruitName: 'Banana',
fruitPrice: 'Rs. 105',
fruitShortDescription: 'Ripe & Tasty',
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => FruitsPage()),);
},
),
}
// Second File
import 'package:flutter/material.dart';
class FruitsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(),
),
);
}
}
Both the code are in different files.
FruitsListingCards is a widget that has a Gesture detector functionality. onTap is a parameter that takes function.
I am using FruitsListingCards in the main file and whenever a user taps on it, should go to the FruitsPage screen. But the error is not letting me to do so. Any solution with proper explanantion will help me a lot.
EDIT:
For proper understanding of code, check my repo:
https://github.com/RaghavTheGreat1/fruits_delivery/tree/master/lib
You have to provide context some how, so that it can connect the last screen and next screen.
You can wrap inside a function for that.
Following minimal code will help you more to understand.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class FruitsPage extends StatelessWidget {
final Function call;
FruitsPage({this.call});
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("press"),
onPressed: call,
),
);
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Text("FruiysPage"),
),
),
);
}
}
callme(context) {
Map<String, Widget> fruits = {
"banana": FruitsPage(
call: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewPage()),
);
},
)
};
return fruits;
}
Well, I figured out the problem on omy own and it was easier than what other people had suggested me even if they haven't looked into my code committed in my GitHub repo.
The quick solution or fix was to add Navigator.push(context, MaterialPageRoute(builder: (context) => fruitsPage)); inside the anonymous function of GestureDetector in FruitsListingCards and then returning fruitsPage(which is a dynamic variable that will take Class object as parameter).