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])),
],
);
}),
),
);
}
}
Related
Assumption and what I want to achieve
I want to make it so that after inputting into the TextField of the TodoAddPage class, it will be displayed in the TodoListPage class like a "TODO list".
Problems and error messages that are occurring
It looks like the following image.
! image description
The corresponding source code.
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// app name
title: 'My Todo App',
theme: ThemeData(
// theme color
primarySwatch: Colors.blue,
),
// display the list list screen
home: TodoListPage(),
);
}
}
// Widget for list list screen
class MyTodoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Todo App',
// App name
theme: ThemeData(
// theme color
primarySwatch: Colors.blue,
),
// Display the list list screen
home:
TodoListPage()
);
}
}
// Widget for list list screen
class TodoListPage extends StatefulWidget {
#override
_TodoListPageState createState() => _TodoListPageState();
}
class _TodoListPageState extends State<TodoListPage> {
// Todo list data
List<String> todoList = [];
#override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
double _height = MediaQuery.of(context).size.height;
return Scaffold(
// Display the AppBar and set the title
appBar: AppBar(
title: Text('List of Lists'),
),
body: Container(
height: _height,
width: _width,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xffe4a972).withOpacity(0.6),
const Color(0xff9941d8).withOpacity(0.6), const Color(0xff9941d8).withOpacity(0.6),
],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
child: ListView.builder(
itemCount: todoList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(todoList[index]),
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// "push" to transition to new screen
// receive the value passed from the add list screen
final newListText = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
// specify the list add screen as the destination screen
return TodoAddPage();
}),
);
if (newListText ! = null) () {
// note that newListText will be null if we cancel it
setState(() {
// add list
todoList.add(newListText);
});
};
},
child: Icon(Icons.add),
),
// Create a ListView based on the data
);
}
}
// Widget for list add screen
class TodoAddPage extends StatefulWidget {
#override
_TodoAddPageState createState() => _TodoAddPageState();
}
class _TodoAddPageState extends State<TodoAddPage> {
// Have the input text as data
String _text = '';
// Widget to display based on the data
#override
Widget build(BuildContext context) {
return
Scaffold(
appBar: AppBar(
title: Text('Add list'),
),
body: Container(
// Add margins
padding: EdgeInsets.all(64),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Display the input text
Text(_text, style: TextStyle(color: Colors.blue)),
const SizedBox(height: 8),
// Input text
TextField(
// receive the value of the input text (value is the input text)
onChanged: (String value) {
// notify that the data has changed (refresh the screen)
setState(() {
// change the data
_text = value;
});
},
),
const SizedBox(height: 8),
Container(
// expand to full width
width: double.infinity,
// add list button
child: ElevatedButton(
onPressed: () {
// "pop" to go back to the previous screen
// pass the data from the "pop" argument to the previous screen
Navigator.of(context).pop(_text);
},
child: Text('add list', style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 8),
Container(
// expand to full width
width: double.infinity,
// cancel button
child: TextButton(
// what to do when the button is clicked
onPressed: () {
// "pop" to go back to the previous screen
Navigator.of(context).pop();
},
child: Text('cancel'),
),
),
],
),
)
);
}
}
```
### Things I've tried
I've enclosed the Widget in a SingleChildScrollView.
However, when I press the button, it goes blank.
I am building a setting widget in my flutter app and I am getting this error :
Error: Could not find the correct Provider above this SettingsForm Widget
Update Adding whole debug snippet:
Launching lib\main.dart on AOSP on IA Emulator in debug mode...
════════ Exception caught by widgets library ═══════════════════════════════════
The following ProviderNotFoundException was thrown building SettingsForm(dirty, state: _SettingsFormState#c73b8):
Error: Could not find the correct Provider<MyUser> above this SettingsForm Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that SettingsForm is under your MultiProvider/Provider<MyUser>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
The relevant error-causing widget was
SettingsForm
When the exception was thrown, this was the stack
#0 Provider._inheritedElementOf
#1 Provider.of
#2 _SettingsFormState.build
#3 StatefulElement.build
#4 ComponentElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
i UPDATED IT AND ADDED SOME EXTRA CODE SO YOU CAN SEE BETTER
setting.dart:
class SettingsForm extends StatefulWidget {
#override
_SettingsFormState createState() => _SettingsFormState();
}
class _SettingsFormState extends State<SettingsForm> {
final _formKey = GlobalKey<FormState>();
final List<String> sugars = ['0', '1', '2', '3', '4'];
final List<int> strengths = [100, 200, 300, 400, 500, 600, 700, 800, 900];
// form values
String? _currentName;
String? _currentSugars;
int? _currentStrength;
#override
Widget build(BuildContext context) {
MyUser user = Provider.of<MyUser>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData? userData = snapshot.data;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
'Update your brew settings.',
style: TextStyle(fontSize: 18.0),
),
SizedBox(height: 20.0),
TextFormField(
initialValue: userData!.name,
decoration: textInputDecoration,
validator: (val) =>
val!.isEmpty ? 'Please enter a name' : null,
onChanged: (val) => setState(() => _currentName = val),
),
SizedBox(height: 10.0),
DropdownButtonFormField<String>(
value: _currentSugars ?? userData.sugars,
decoration: textInputDecoration,
items: sugars.map((sugar) {
return DropdownMenuItem(
value: sugar,
child: Text('$sugar sugars'),
);
}).toList(),
onChanged: (val) => setState(() => _currentSugars = val),
),
SizedBox(height: 10.0),
Slider(
value: (_currentStrength ?? userData.strength).toDouble(),
activeColor:
Colors.brown[_currentStrength ?? userData.strength],
inactiveColor:
Colors.brown[_currentStrength ?? userData.strength],
min: 100.0,
max: 900.0,
divisions: 8,
onChanged: (val) =>
setState(() => _currentStrength = val.round()),
),
ElevatedButton(
style:
ElevatedButton.styleFrom(primary: Colors.pink[400]),
child: Text(
'Update',
style: TextStyle(color: Colors.white),
),
onPressed: () async {
if (_formKey.currentState!.validate()) {
await DatabaseService(uid: user.uid).updateUserData(
_currentSugars ?? snapshot.data!.sugars,
_currentName ?? snapshot.data!.name,
_currentStrength ?? snapshot.data!.strength);
Navigator.pop(context);
}
}),
],
),
);
} else {
return Loading();
}
});
}
}
UPDATE: I AM INCLUDING THE HOME.DART FILE THAT INCLUDES THE 'SETTINGFORM' WIDGET
home.dart :
class Home extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
void _showSettingsPanel() {
showModalBottomSheet(context: context, builder: (context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: SettingsForm(), <-- Here
);
});
}
return StreamProvider<List<Brew>?>.value(
value: DatabaseService(uid: '').brews,
initialData: null,
child: Scaffold(
backgroundColor: Colors.brown[50],
appBar: AppBar(
title: Text('Brew Crew'),
backgroundColor: Colors.brown[400],
elevation: 0.0,
actions: <Widget>[
TextButton.icon(
icon: Icon(Icons.person),
label: Text('logout'),
onPressed: () async {
await _auth.signOut();
},
),
TextButton.icon(
icon: Icon(Icons.settings),
label: Text('settings'),
onPressed: () => _showSettingsPanel(),
)
],
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/coffee_bg.png'),
fit: BoxFit.cover,
),
),
child: BrewList()
),
),
);
}
}
user.dart:
class MyUser {
final String uid;
MyUser({ required this.uid });
}
class UserData {
final String uid;
final String name;
final String sugars;
final int strength;
UserData({ required this.uid, required this.sugars, required this.strength, required this.name });
}
Update Update 2 Error
Because you have to declare the provider class above the class were your using it , if u find this ans crt mark it as crt
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => MyUser(),),
],
child: MaterialApp());
You have to warp the parent class with the provider class you are using inside. For doing so the easiest way is to add a static method in widget havingMaterialPageRoute which helps to navigate to SettingsForm screen.
class SettingsForm extends StatefulWidget {
static Widget getWidget() {
return new Provider(
create: (_) => MyUser(),
child: ChangeNotifierProvider(
create: (BuildContext context) => MyUser(),
builder: (_,_) => SettingsForm()
),
);
}
#override
_SettingsFormState createState() => _SettingsFormState();
}
To open SettingsForm screen just call getRoute function on button pressed. Check the below code.
Open SettingsForm screen from Home screen
void _showSettingsPanel() {
showModalBottomSheet(context: context, builder: (context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: SettingsForm.getWidget(), <-- Here
);
});
}
Im building a list from Firestore collection stream using ListViewBuilder, each item is a Text widget in the list, Im trying to change color of the text onTap the Text widget,
Item 1
Item 2
Item 3
when touching/onTap Item1, text color of Item1 should be changed
I implemented using GestureDetector with setState but on onTap>setState execution, the listview of the stream is rebuilt, giving a second of blank screen/flicker and loosing the actual state since its refreshed
var _dynamicTextColor = Colors.green;
return ListView.builder(
reverse: true,
itemCount: itemStream.length,
itemBuilder: (context, itemIndex) => Container(
child:GestureDetector(
onTap: (){
setState(() {
_dynamicTextColor = Colors.white;
});
},
child: Text(itemStream[itemIndex]['title'], style: TextStyle(color:_dynamicTextColor),),
),
),
);
You can try the following. This will use the selected property to decide which container should be blue.
class _TestState extends State<Test> {
String selected = "first";
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
selected = 'first';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'first' ? Colors.blue : Colors.transparent,
child: Text("First"),
),
),
GestureDetector(
onTap: () {
setState(() {
selected = 'second';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'second' ? Colors.blue : Colors.transparent,
child: Text("Second"),
),
),
],
);
}
}
I recommend you use a state management, for example Provider.
I prepared an example, I hope you understand it and solve your problem.
First, add the provider package to pubspec.yaml
provider: ^4.3.3
Then, create a class where you will manage the states, as you would with setState. The code would be like this:
class ColorProvider extends ChangeNotifier {
ColorProvider();
bool isPressed = true;
Color color = Colors.black;
changeColor() {
if (isPressed == true) {
color = Colors.green;
isPressed = false;
} else {
color = Colors.black;
isPressed = true;
}
notifyListeners();
return isPressed;
}
}
Once this is done, you must Wrap the App material in a ChangeNotifierProvider widget:
import 'package:flutter/material.dart';
import 'package:flutter_application_1/home_page.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ColorProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(),
),
);
}
}
And finally, call the Provider in the widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final providerWatch = context.watch<ColorProvider>();
final providerRead = context.read<ColorProvider>();
String text = 'This is an example text';
return Scaffold(
body: Container(
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => providerRead.changeColor(),
child: Text(
text,
style: TextStyle(color: providerWatch.color),
)),
),
));
}
}
and this would be the result:
I am writing a flutter program where the user should select a value from a DropdownButtonFormField. once the selection is made, the choice should be displayed on the dropdown. I use a push route to get the data from a second screen in which the choice is utilized. My problem is after selecting the option, the page refreshes and therefore doesnt show the selected value on the dropdown.
Below is my code:
I create the Dropdownbuttonformfield in a file called shared.dart so I can call it in multiple files:
class UserDropdownList extends StatefulWidget {
#override
_UserDropdownListState createState() => _UserDropdownListState();
}
class _UserDropdownListState extends State<UserDropdownList> {
String currentUser;
#override
Widget build(BuildContext context) {
final user = Provider.of<List<User>>(context) ?? [];
return DropdownButtonFormField(
isExpanded: true,
decoration: textInputDecoration,
value: currentUser,
hint: Text(
'Incoming Officer',
),
onChanged: (val) {
setState(() => currentUser = val);
var route = MaterialPageRoute(
builder: (BuildContext context) =>
FinalForm(chosenUser: currentUser,)
);
Navigator.of(context).push(route);
},
// onChanged: (val) => setState(() => currentUser = val),
items: user.map((user){
return DropdownMenuItem(
value: user.userId,
child: Text(user.name)
);
}).toList(),
);
}
}
I then call the Custom button in my main page like so
class FinalForm extends StatefulWidget {
//code for importing selected user
final String chosenUser;
FinalForm({Key key, this.chosenUser}) : super (key: key);
#override
_FinalForm createState() => _FinalFormState();
}
class _FinalFormState extends State<FinalForm> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Final Form')
),
body: Form(
child: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.fromLTRB(5, 5, 5, 5),
children: <Widget>[
SizedBox(height: 20.0),
Align(
child: Text(
'Select Incoming Officer',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
)
),
SizedBox(height: 20.0),
StreamProvider<List<User>>.value(
value: DatabaseService().users,
child: UserDropdownList(),
),
SizedBox(height: 20.0),
Text("${widget.chosenUser}"),
],),
),
),
);
}
}
Is there a way to keep the selected value on the dropdown or prevent the screen from reloading?
If you are navigating away from the current page / view, it would make sense for the current dropdown selection to be lost. You can pass the current selection as an argument to the push function to redisplay on the new page. Hth
I have an app which has two tabs. One of the which is the "SAVED ITEMS" tab. When I save the items (from a different screen of ALL ITEMS LIST) it gets saved and on even switching the tabs works fine. But when I close the app and reopen it, the "SAVED ITEMS" list is empty and I've to select the items again. I've used the AutomaticKeepAliveClientMixin but its not helping. Any idea on how to solve this folks?
My code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blue,
accentColor: Colors.white,
),
home: DefaultTabController(
length: 2,
child: Scaffold(
drawer: Drawer(),
backgroundColor: Colors.blueAccent,
appBar: AppBar(
backgroundColor: Colors.blueAccent,
title: Text('AIO'),
bottom: TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.search)),
Tab(icon: Icon(Icons.favorite)),
],
),
),
body: TabBarView(
children: <Widget>[
gridView,
SecondPage(),
],
),
),
),
);
}
#override
bool get wantKeepAlive => true;
}
SecondTab code:
Set<int> favorites = {};
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Stack(
fit: StackFit.expand,
children: <Widget>[
_getFavoriteList(),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.blue,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditFavorites(),
),
).then((updatedFavorites) {
if (updatedFavorites != null)
// setState(() {
favorites = updatedFavorites;
// });
});
},
),
),
)
],
);
}
Add shared_preferences dependency in pubspec.yaml
Update your main()
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences pref = await SharedPreferences.getInstance();
pref.getStringList("favorites")?.forEach((fav){
favorites.add(int.tryParse(fav));
});
runApp(MyApp());
}
then update your SecondPage
Set<int> favorites = {};
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
Future<void> _upDateFavorites(Set<int> updatedFavorites) async {
print("secong: u$updatedFavorites");
SharedPreferences pref = await SharedPreferences.getInstance();
List<String> favoritesAsString =
updatedFavorites.map((fav) => fav.toString()).toList(); //TODO: Change `favorites` to `updatedFavorites`
print(favoritesAsString);
await pref.setStringList("favorites", favoritesAsString); //TODO: await here to store it completely
favorites = updatedFavorites;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: <Widget>[
_getFavoriteList(),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.blue,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditFavorites(),
),
).then((updatedFavorites) async {
if (updatedFavorites != null)
// setState(() {
_upDateFavorites(updatedFavorites);
// });
});
},
),
),
)
],
);
}
Widget _getFavoriteList() {
if (favorites?.isNotEmpty == true)
return _FavoriteList();
else
return _EmptyFavoriteList();
}
}
Don't forget to import 'package:shared_preferences/shared_preferences.dart'; where you use SharedPreferences
Avoid storing widget in a vaiable, if it needs to be rebuild.
So delete var favGridView = GridView.builder( ....
class _FavoriteList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: favorites.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
print("kkkkkkkkkkk: ${favorites.elementAt(index)}");
return InkWell(
child: Card(
elevation: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10),
child: Container(
child: Image.asset(
'lib/images/${images[favorites.elementAt(index)]}'), //TODO: Change this
// child: SizedBox(child: Text('yashjha'),),
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage('lib/images/${images[index]}'),
// fit: BoxFit.fitWidth,
// alignment: Alignment.topCenter,
// ),
// ),
),
),
Text(nameOfSite[favorites.elementAt(index)]), //TODO: Change This
],
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Text("dsadsa")),
);
},
);
},
);
}
}
I also recommend using actions instead of FloatingButton in _EditFavoritesState. Because the floating button hides last item. which makes it unable to add to favorites
AutomaticKeepAliveClientMixin is used to retain the data when the app is running and you need to retain data while switching between tabs, pages etc.
I would suggest you use SharedPreferences to easily save data to your memory that can be retrieved when the app is started again.
SharedPreferences: https://pub.dev/packages/shared_preferences