Change notifier not updating stateless widget page - android

I am trying to update page content when an action is called.
Below is a sample of the code under change notifier that receives the onTap: method, When I click it should update the stateless widget depending on the tab clicked.
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: ScreenChange()),
ChangeNotifierProvider(create: (context) => UserRepository.instance())
],
child: Consumer<ScreenChange>(
builder: (context, ScreenChange screenChange, child) {
return Material(
child: SafeArea(
child:
InkWell(
onTap: () {
screenChange.changeHomeState(PageState.homescreen);
print("${screenChange.state}");
},
child: ListTile(
leading: Container(
decoration: BoxDecoration(boxShadow: <BoxShadow>[
BoxShadow(
color:
screenChange.state == PageState.homescreen
? Global.orange
: Colors.transparent,
blurRadius: blurRadius,
spreadRadius: spreadRadius,
offset: const Offset(5, 5),
)
]),
child: const Icon(
FontAwesomeIcons.home,
color: Global.yellow,
),
),
title: Text(
"Home",
style: GoogleFonts.acme(),
),
),
),
Below is the widget that I want to change/ update:
ChangeNotifierProvider(
create: (context) => ScreenChange(),
child: Consumer<ScreenChange>(
builder: (context, ScreenChange change, child) {
switch (change.state) {
case PageState.homescreen:
print('${change.state}');
return Home();
case PageState.categories:
print('${change.state}');
return const CategoryScreen();
case PageState.notification:
print('${change.state}');
return NotificationScreen();
default:
}
return Center(
child: Text("Something went wrong",
style: GoogleFonts.abel(
fontSize: 20,
)));
}),
),
Below is my change notifier class:
enum PageState {
homescreen,
categories,
account,
notification,
about,
shareApp
}
class ScreenChange extends ChangeNotifier {
PageState homeState = PageState.homescreen;
PageState get state => homeState;
void changeHomeState(PageState state) {
homeState = state;
notifyListeners();
}
}

I found the solution here , I was using
another change notifier provider which did not trigger ,
Therfore my solution: I MOVED MY MULTIPROVIDER ABOVE THE WIDGET I WAS TRING TO UPDATE
https://github.com/rrousselGit/provider/issues/231

Related

How to access dynamic input fields values on button click in flutter

I am working on an attendance application where I assign wages to the workers. I want to store all the wages given to the workers into the database. But the problem is I want to access all the given values on button click. I have no idea how it can be done in flutter. I am a beginner.
I have given all the codes and the image of what output i want.
Image of Emulator
Here is my code...
ATTENDANCE SCREEN
...rest code...
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Upload Patti'),
content: SingleChildScrollView(
child: ListBody(
children: [
TextFormField(
controller: _mainWagesController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Amount",
prefixIcon: Icon(Icons.wallet, color: Colors.blue),
),
),
],
),
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.pop(context);
newWages = _mainWagesController.text;
setState(() {});
},
child: const Text("Assign Wages"),
),
],
);
},
);
},
child: const Icon(Icons.check_circle),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.00),
child: Column(children: [
const SizedBox(
height: 20,
),
Center(
child: Text(
"Date : ${DateFormat.yMMMEd().format(DateTime.parse(widget.attendanceDate.toString()))}",
style: const TextStyle(fontSize: 20),
),
),
const SizedBox(
height: 20,
),
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['hamal'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: data[index]['worker_id'],
name: data[index]['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
});
} else if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong !"),
);
} else {
return const Center(child: LinearProgressIndicator());
}
},
),
]),
),
),
...rest code
widget
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 10,
height: 50,
),
const Icon(FeatherIcons.user),
const SizedBox(
width: 20,
),
Text(
widget.name,
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
// onChanged: _onChangeHandler,
initialValue: widget.wages.toString(),
decoration: const InputDecoration(
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
],
)
]),
);
}
I suggest you use a StateManager for your application, for example GetX
is a good solution. Create a controller file like the below:
// define this enum outside of class to handle the state of the page for load data
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
class AttendanceCntroller extends GetxController{
RxList<dynamic> dataList=RxList<dynamic>();
#override
void onInit() {
//you can write other codes in here to handle data
pageState(AppState.loading);
dataList.value=
SupervisorAttendanceServices.getAttendancesDetailsList(attendanceId);
pageState(AppState.loaded);
super.onInit();
}
}
and in your view(UI) page, handle it in this way:
class AttendanceView extends GetView<AttendanceCntroller>{
#override
Widget body(BuildContext context) {
// TODO: implement body
return Obx( ()=> controller.pageState.value==AppState.loading ? const
Center(child: LinearProgressIndicator()) : ListView.builder(
itemCount: controller.dataList.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: controller.dataList['worker_id'],
name: controller.dataList['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: controller.dataList[index]
['attendance_worker_presense']
.toString());
})
)
}
}
for more data read the GetX link and read clean architecture with the GetX sample repository of my GitHub it have advanced management of states with GetX with dependency injection handling.
If you want to have prefilled value in TextFormField, you can either use initialValue or controller parameter.
The value of controller parameter will help you to get/update the value of TextFormField.
For controller parameter refer below.
TextEditingController controller = TextEditingController(text: 'This is text will be pre-filled in TextFormField');
...
TextFormField(
controller: controller,
);
Create List or Map of those controllers.
List<TextEditingController> listOfControllers = [ controller1, controlle2,...];
Use for loop through this List on onClick() method of Button.
ElevatedButton(
onPressed: () {
for(var controllerItem in listOfControllers) {
print(controllerItem.text); // the value of TextFormField
}
},
)

_TypeError (type 'Null' is not a subtype of type 'String')

I don't know what happened, but I tried to fix this by making it nullable, but it didn't work.
I wanted to view elements from the database, therefore i put them in "for" loop..
but it still showing me exception _TypeError (type 'Null' is not a subtype of type 'String')
So what should I do to fix this?
This is a screenshot of the exception:
enter image description here
And this is my code:
`import 'pac`kage:blackboard/view/Teacher/Addcourse1.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:blackboard/constraints/textstyle.dart';
import 'package:flutter/material.dart';
import 'package:blackboard/setting/colors.dart';
class CoursesT extends StatefulWidget {
const CoursesT({Key? key}) : super(key: key);
#override
State<CoursesT> createState() => _CoursesTState();
}
class _CoursesTState extends State<CoursesT> {
// Getting Student all Records
final Stream<QuerySnapshot>? studentRecords =
FirebaseFirestore.instance.collection('CourseStudent').snapshots();
// For Deleting Users
CollectionReference? delUser =
FirebaseFirestore.instance.collection('CourseStudent');
Future<void> _delete(id) {
return delUser!
.doc(id)
.delete()
.then((value) => print('User Deleted'))
.catchError((_) => print('Something Error In Deleted User'));
}
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: studentRecords,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
print('Something Wrong in HomePage');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Storing Data
final List? firebaseData = [];
snapshot.data?.docs.map((DocumentSnapshot documentSnapshot) {
Map store = documentSnapshot.data() as Map<String, dynamic>;
firebaseData!.add(store);
store['id'] = documentSnapshot.id;
}).toList();
return Scaffold(
appBar: AppBar(
backgroundColor: BBColors.primary6,
title: Text("Your Courses"),
leading: Icon(Icons.menu, color: Colors.white),
actions: [
Icon(
Icons.search,
),
SizedBox(
width: 20,
),
],
),
body: Container(
margin: const EdgeInsets.all(8),
child: SingleChildScrollView(
child: ListView(
shrinkWrap: true,
children: [
for (var i = 0; i < firebaseData!.length; i++) ...[
Card(
elevation: 4.0,
child: Column(
children: [
ListTile(
title: Text(
firebaseData[i]['Course Title'],
),
subtitle: Text(
firebaseData[i]['Course Group'],
),
trailing: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AddCourse1(),
),
);
},
icon: const Icon(
Icons.add,
color: BBColors.bg1,
),
),
),
Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.centerLeft,
child: Text(
firebaseData[i]['Course Description'],
),
),
ButtonBar(
children: [
// IconButton(
// onPressed: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => EditPage(
// docID: firebaseData[i]['id'],
// ),
// ),
// );
// },
// icon: const Icon(
// Icons.edit,
// color: Colors.orange,
// ),
// ),
IconButton(
onPressed: () {
_delete(firebaseData[i]['id']);
//print(firebaseData);
},
icon: const Icon(
Icons.delete,
color: Colors.red,
),
),
],
)
],
)),
], //this is loop
],
),
),
),
);
});
}
}
just check wheather you are getting data from firebase in firebaseData variable and also refactor you code like this
subtitle: Text(firebaseData[i]['Course Group']??"Some Text",),
If your variable return null then it will print the hard-coded text on the right and save app from crashing.
Everything related services at any point might received as "null". I suggest making any variable depends on internet interaction, nullable. So when you use them you can have placeholder values.
For example
String? theUserNameFetchedFromInternet
// while using
Text(theUserNameFetchedFromInternet ?? "john")
This type of handling prevents lots of crash over-time & is there is why dart is null-safety.

how to handle the back button of andriod in flutter

I am working on an flutter application where it has more than 5 screens, I want to handle the back button of android, for example, when I logged in on app, it display the dashboard screen, so when I move to profile and then move to history screen, and when I click on back button on history screen it should navigate to profile screen, because the last screen I visited before history screen is profile, but it display the first screen which login screen.
I found the solution which works like when I click on back button it close the app.
Update:
My screens are navigating from drawer and from bottom navigation, there is only login screen where i use login button and calling dashboard screen onpressed function, other than this there is no button on any screen which navigate to other screens. here is the code for drawer and bottom navigation.
this is line of code i am using on login button
Navigator.push(
context, new MaterialPageRoute(builder: (context) => Employee()));
drawer and bottom navigation code:
Code:
class Employee extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primaryColor: Colors.blue
),
home: EmployeeNavigation(),
);
}
}
int _selectedTab = 0;
final _pageOptions = [
EmployeeDashboard(),
location(),
Profile()
];
String getname="";
String getemail="";
String getdesignation="";
String getaccesstoken="";
String getdate;
String getTime;
// ignore: must_be_immutable
class EmployeeNavigation extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return EmployeeNavigationState();
}
}
class EmployeeNavigationState extends State<EmployeeNavigation> {
var email;
var designation;
var date;
bool valuefirst = false;
String backtext="";
#override
Widget build(BuildContext context) {
//i used this too but it doesn't work.
return WillPopScope(
onWillPop: () async {
if (_selectedTab == 0) {
return true;
}
setState(() {
_selectedTab = 0;
});
return false;
},
child:Scaffold(
drawer:Emp_DrawerCode(),
body: _pageOptions[_selectedTab],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.blue[50],
type: BottomNavigationBarType.fixed,
currentIndex: _selectedTab,
onTap: (value) {
print(value);
setState(() {
_selectedTab = value;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.location_on), label: "Location"),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: "Profile",
),
],
)));
}
}
class Emp_DrawerCode extends StatefulWidget {
#override
_Emp_DrawerCodeState createState() => _Emp_DrawerCodeState();
}
class _Emp_DrawerCodeState extends State<Emp_DrawerCode> {
SharedPreferences myPrefs;
name() async{
myPrefs=await SharedPreferences.getInstance();
setState(() {
getname=myPrefs.getString('name');
getemail=myPrefs.getString('email');
getdesignation=myPrefs.getString('designation');
});
}
void initState(){
name();
}
#override
Widget build(BuildContext context) {
return new Drawer(
child: new ListView(
padding: const EdgeInsets.all(0.0),
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: new Text(getname),
accountEmail: new Text(getemail),
currentAccountPicture: new CircleAvatar(
backgroundColor:
Theme.of(context).platform == TargetPlatform.android
? Colors.white
: Colors.blue,
child: Text(
getname[0][0],
style: TextStyle(fontSize: 40.0),
),
),
),
new ListTile(
title: new Text('Home'),
leading: Icon(Icons.dashboard,color:Colors.grey),
onTap: (){
Navigator.pop(context);
Navigator.push(context, new MaterialPageRoute(
builder: (context)=> EmployeeNavigation()
)
);
},
),
new ListTile(
title: new Text('Request for leave'),
leading: Icon(Icons.request_page,color:Colors.grey),
onTap: (){
Navigator.pop(context);
Navigator.push(context, new MaterialPageRoute(
builder: (context)=>RequestForLeave()
)
);
},
),
new ExpansionTile(
title: new Text('History'),
children: <Widget>[
ListTile(
title:new Text("My Attendance"),
leading: Icon(Icons.assessment_outlined ,color:Colors.grey),
onTap: (){
Navigator.pop(context);
Navigator.push(context, new MaterialPageRoute(
builder: (context)=>new MyAttendance()
)
);
},
),
ListTile(
title:new Text("Leaves"),
leading: Icon(Icons.assessment_outlined,color:Colors.grey ),
onTap: (){
Navigator.pop(context);
Navigator.push(context, new MaterialPageRoute(
builder: (context)=>new LeaveHistory()
)
);
},
),
],
leading: Icon(Icons.history,),
),
new ListTile(
title: new Text('Log out'),
leading: Icon(Icons.logout,color:Colors.grey),
onTap: (){
myPrefs.setBool('login', true);
Navigator.pop(context);
Navigator.push(context, new MaterialPageRoute(
builder: (context)=>
Login()
)
);
},
),
],
),
);
}
}
kindly please help how to do this.
I am using navigator.push method and it is acting as you want.
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.blue),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Sample"),
centerTitle: true,
),
I wish it solve your problem.
You need to first clarify your question a bit more.
Are using page view/ are you talking about a scenario where all are separately navigable screens or some different scenario ?
I am considering it as the second scenario when all are separately navigable screens.
In that case, every time user navigates to next screen you must use Navigator.pushNamed() / Navigator.push() as for now I think you are using Navigator.pushReplacement() which is causing this issue probably.
Navigator is nothing but a class aware of the stack of screens in the memory and so are the functions it provides us with. A simple push would mean pushing over the last pushed screen whereas pushing a replacement would replace the last pushed screen ultimately preventing you from navigating to the last pushed screen. Exactly like how it would work for a stack data structure.
Firstly Wrap your Scaffold with WillPopScope
return WillPopScope(
onWillPop: _onBackPressed,
child : Scaffold());
And then you can call the Function that handles the back press.
// Back Button Android Behaviour
Future<bool> _onBackPressed() async {
final shouldPop = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
"Are you sure you want to leave this page?",
style: TextStyle(
color: Colors.black,
fontSize: 25.0,
fontWeight: FontWeight.w500,
),
),
actions: <Widget>[
SizedBox(width: 16),
InkWell(
onTap: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) => HomeScreen(), // Destination
),
(route) => false,
);
},
child: Container(
padding: EdgeInsets.all(8.0),
child: Text(
"LEAVE",
style: TextStyle(
color: Colors.red,
fontSize: 20.0,
fontWeight: FontWeight.w500,
),
),
),
),
SizedBox(width: 8.0),
InkWell(
onTap: () => Navigator.of(context).pop(false),
child: Container(
padding: EdgeInsets.all(8.0),
child: Text(
"DO NOT LEAVE",
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
fontWeight: FontWeight.w500,
),
),
),
),
],
));
return shouldPop ?? false;
}

Problem if state in DropDown menu in flutter

I'm having trouble managing the status of my dropdowns,
I have two dropDowns, the items of the second are built based on the item selected in the first.
My problem is being to clean the second one when I change the option of the first one again.
I tried to set the value of the second to null at the time if I update the value of the first but even though it is still giving problem.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobicar/app/stores/brand_store.dart';
import 'package:mobicar/app/stores/vehicle_store.dart';
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var brandStore = BrandStore();
var vehicleStore = VehicleStore();
var selectedBrand;
var selectedVehicle;
#override
void initState() {
brandStore.getBrands().then((value) {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _body(),
);
}
Column _body() {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => _newItemDialog(),
child: Container(
margin: EdgeInsets.only(top: 5, right: 5),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: Text(
"new",
style: TextStyle(color: Colors.white),
),
),
),
],
),
Expanded(
child: ListView(
children: [Text("Supervisor, selecione a viatura")],
))
],
);
}
Future _newItemDialog() {
return showDialog(
barrierDismissible: false,
context: context,
builder: (context) => Container(
child: AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Novo Veiculo "),
IconButton(
icon: Icon(Icons.close),
onPressed: () => Navigator.pop(context))
],
),
content: Observer(builder: (_) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/images/car.png"),
_dropdownButton(
hint: "Marca",
itemlist: _dropDownItemBrands,
type: 'brand'),
_dropdownButton(
hint: "Modelo",
itemlist: _dropDownItemVehicles,
type: 'vehicle'),
_dropdownButton(
hint: "Ano", itemlist: _dropDownItemBrands, type: ''),
],
);
}),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
));
}
_dropdownButton({required hint, required itemlist, required type}) {
return Container(
margin: EdgeInsets.only(top: 10),
child: DropdownButtonFormField(
hint: Text(hint),
items: itemlist() ?? [],
onChanged: (value) {
switch (type) {
case 'brand':
vehicleStore.getVehicles(value);
break;
case 'vehicle':
selectedVehicle = value;
break;
default:
}
},
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 8, right: 0, top: 0, bottom: 0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(4)),
),
),
);
}
List<DropdownMenuItem<int>> _dropDownItemBrands() {
List<DropdownMenuItem<int>> list = [];
if (brandStore.brandList.isNotEmpty) {
brandStore.brandList.forEach((element) {
list.add(
DropdownMenuItem<int>(
child: Text(
element.name,
style: TextStyle(color: Colors.black),
),
value: element.id,
),
);
});
return list;
} else {
return list;
}
}
List<DropdownMenuItem<int>> _dropDownItemVehicles() {
List<DropdownMenuItem<int>> list = [];
if (brandStore.brandList.isNotEmpty) {
vehicleStore.vehiclesList.forEach((element) {
list.add(
DropdownMenuItem<int>(
child: Text(
element.name,
style: TextStyle(color: Colors.black),
),
value: element.id,
),
);
});
return list;
} else {
return list;
}
}
}
When you want to change clean the values of the second dropdown menu on your UI, you have to use setState.
So,
I tried to set the value of the second to null at the time if I update
the value of the first but even though it is still giving problem.
Set it to null or an empty list [], but inside of setState:
//instead of yourSecondValue == null, use:
setState(() {
yourSecondValue == null;
});
This is assuming that setting it to null will actually solve the problem.

Use Provider to update Scaffold from a second screen

I want to have a Settings screen where I can choose a color to be returned to the first screen.
I can't get the first screen to update when the Setting screen is closed.
I'm using the Provider as a change notifier. But I can't see how to trigger the update of the first screen. The third button creates an event which updates the screen, but can this be done automatically?
What am I missing...?
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
Color bgColor = Colors.yellow[100];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomeScreen());
}
}
class MyHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: Consumer<ColorModel>(builder: (context, colorModel, child) {
return Scaffold(
appBar: AppBar(title: Text('Thanks for your help :)')),
body: Container(
constraints: BoxConstraints.expand(),
color: bgColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Change background color on this screen'),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Button1', style: TextStyle(color: Colors.white)),
onPressed: () {
var result = Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2()));
print('>>> Button1-onPressed completed, result=$result');
},
),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Choose a colour', style: TextStyle(color: Colors.white)),
onPressed: () {
asyncButton(context);
print('>>> Screen1 Button-onPressed completed');
},
),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Now try me', style: TextStyle(color: Colors.white)),
onPressed: () {
colorModel.notifyListeners();
},
),
],
),
),
);
}),
);
}
void asyncButton(BuildContext context) async {
var result = await Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2()));
print('>>> asyncButton completed: result = $result');
bgColor = result;
}
}
class ColorModel with ChangeNotifier {
void updateDisplay() {
notifyListeners();
}
}
class Screen2 extends StatelessWidget {
int _value;
List<String> names = ['Red', 'Green', 'Blue'];
List<Color> colors = [Colors.red[100], Colors.green[100], Colors.blue[100]];
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: Scaffold(
appBar: AppBar(
toolbarHeight: 80,
backgroundColor: Colors.blue,
title: Center(child: Text('Screen2')),
),
body: Container(
constraints: BoxConstraints.expand(),
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Consumer<ColorModel>(builder: (context, colorModel, child) {
return DropdownButton(
value: _value,
hint: Text("Select a color"),
focusColor: Colors.lightBlue,
onChanged: (int value) {
Navigator.pop(context, colors[value]);
},
items: [
DropdownMenuItem(value: 0, child: Text(names[0])),
DropdownMenuItem(value: 1, child: Text(names[1])),
DropdownMenuItem(value: 2, child: Text(names[2])),
],
);
}),
],
),
),
),
);
}
}
Navigator.push is tricky to use with Provider. It causes a lot of "Could not find the correct Provider above this Navigator Widget" errors. I've explained why in this answer to a related question.
Here's a quick overview of your situation:
Provider Scope
Architecture in question code:
MaterialApp
> provider(Screen A)
> provider(Screen B)
Architecture in solution below:
provider(MaterialApp)
> Screen A
> Screen B
Here's your code sample, shortened up, working with Provider, updating the background color on Page 1 from the Page 2.
I've put comments throughout the code to explain changes.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// - global var removed -
// Color bgColor = Colors.yellow[100];
void main() {
runApp(ProviderApp());
}
class ProviderApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
/// Define your Provider here, above MaterialApp
return ChangeNotifierProvider(
create: (context) => ColorModel(),
child: MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: ScreenA()
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Thanks for your help :)')),
body: Container(
constraints: BoxConstraints.expand(),
//
// color: bgColor // - global var removed -
color: Provider.of<ColorModel>(context).bgColor,
// ↑ use your Provider state-stored value here ↑
//
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Change background color on this screen'),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child: Text('Go Screen B', style: TextStyle(color: Colors.white)),
// Navigator.push returns a Future, must async/await to use return value
onPressed: () async {
var result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ScreenB()));
// note that this context is not Screen A context, but MaterialApp context
// see https://stackoverflow.com/a/66485893/2301224
print('>>> Button1-onPressed completed, result=$result');
},
),
],
),
),
);
}
}
/// This is your state object. Store your state here.
/// Create this once and use anywhere you need. Don't re-create this unless
/// you want to wipe out all state data you were holding/sharing.
class ColorModel with ChangeNotifier {
// color is the state info you want to store & share
Color bgColor = Colors.yellow[100]; // initialized to yellow
/// Update your state value and notify any interested listeners
void updateBgColor(Color newColor) {
bgColor = newColor;
notifyListeners();
}
/// - removed - replaced with updateBgColor ↑
/*void updateDisplay() {
notifyListeners();
}*/
}
class ScreenB extends StatelessWidget {
// all fields in StatelessWidgets should be final
//final int value; // this value isn't needed
final List<String> names = ['Red', 'Green', 'Blue'];
final List<Color> colors = [Colors.red[100], Colors.green[100], Colors.blue[100]];
#override
Widget build(BuildContext context) {
/// Instantiating your model & giving it to Provider to should only happen once per
/// Widget Tree that needs access to that state. e.g. MaterialApp for this solution
/// The state object & Provider below was repeated & has been commented out / removed.
/// This was wiping out any previously stored state and creating a new Provider / Inherited scope
/// to all children.
/*return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: ,
);*/
// - end of duplicate Provider removal -
return Scaffold(
appBar: AppBar(
title: Text('Screen2'),
),
body: Container(
alignment: Alignment.center,
child: Consumer<ColorModel>(builder: (context, colorModel, child) {
return DropdownButton(
//value: value, // this value isn't needed
hint: Text("Select a color"),
onChanged: (int value) {
colorModel.updateBgColor(colors[value]);
Navigator.pop(context, colors[value]);
},
items: [
DropdownMenuItem(value: 0, child: Text(names[0])),
DropdownMenuItem(value: 1, child: Text(names[1])),
DropdownMenuItem(value: 2, child: Text(names[2])),
],
);
}),
),
);
}
}

Categories

Resources