How to change state of MaterialPageRoute? - flutter

I was following the tutorial from the Flutter docs where you create a Startup naming app. The app consists in two pages: one where there's an infinite list of randomly generated startup names that you can add to your favorites, and a favorites page where you can see the names you saved.
After completing the tutorial, I tried to add some functionality of my own, I wanted to be able to Unfavorite a name by tapping it on the "Favorites" page. Below is the code that pushes the Favorites page to the navigator:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
},
),
);
}
But it didn't worked as it should: you can indeed unsave names by tapping them, but the changes will only be shown on the screen after you go back to the main page and then to the favorites page again (or in other words, when Builder is called?).
So how do I fix this? Do I need to create a Stateful widget for the favorites page? If yes, how do I pass the _saved set to my new widget?
If anybody needs the whole code:
https://pastebin.com/asLneaKe

Wrap with StatefulBuilder works fine.
You can see full code and working demo
code snippet
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
working demo
full code
import 'package:english_words/english_words.dart' as prefix0;
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
primaryColor: Colors.white,
),
home: RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
RandomWordsState createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final Set<WordPair> _saved = Set<WordPair>();
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Startup Name Generator'), actions: <Widget>[
// Icone 3 linhas
IconButton(
icon: Icon(Icons.list),
onPressed: _pushSaved,
),
]),
body: _buildSuggestions(),
);
}
Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
});
}
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
});
},
),
);
}
}

Related

How do we transfer data from one screen to another screen in flutter?

#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Category',
),
),
body: Center(
child: FutureBuilder(
future: fetchCategory(),
builder: (ctx, snapShot) {
if (snapShot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
return ListView.builder(
itemCount: snapShot.data["table"].length,
itemBuilder: (context, index) {
return ListTile(
title: TextButton(
style: TextButton.styleFrom(
textStyle: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
child: Text(snapShot.data["table"][index]["name"]),
),
//subtitle: Text("price: ${snapShot.data["table"][index]["price"]}"),
);
},
);
}
},
),
),
);
}
I want to transfer this data (snapShot.data["table"][index]["id"]) and use it to display the results on a second screen / data return String /
I want to use the data to display the items that have the same number on the second page, how can I do that
While you are using pushNamed(context, '/second'); and if receiver widget, don't have Constructor to get data, you can pass through ModalRoute like
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetA(),
settings: RouteSettings(
arguments: "id",
)),
);
and received on second screen as
class WidgetA extends StatelessWidget {
static final routeName = "/widgetA";
#override
Widget build(BuildContext context) {
final data = ModalRoute.of(context)!.settings;
late String retriveString;
if (data.arguments == null)
retriveString = "empty";
else
retriveString = data.arguments as String;
return Scaffold(
body: Column(
children: [
Text("Widget A"),
Text("Got data from parent $retriveString"),
],
),
);
}
}
I will suggest you to visit this where I've described different ways passing data.
you can pass data using argument
onPressed: () {
Navigator.pushNamed(context, '/second', arguments: snapShot.data["table"][index]);
},

How could i pass List<T> by reference into a class from other class? dart/flutter

I have several files and
i want to pass 'messages' to 'floatingActionButton: ComposeButton(messages)' by reference and add new message from form.
but my constructor in ComposeButton doesn't work.
In ComposeButton message == null,
Could you gyus advice me what i need to do?
Here all code
MessageList.dart
import 'package:email_clinet/ComposeButton.dart';
import 'package:email_clinet/Message.dart';
import 'package:flutter/material.dart';
import 'MesageDeatail.dart';
class MessagesList extends StatefulWidget {
final String title;
const MessagesList({Key key, this.title}) : super(key: key);
#override
State<StatefulWidget> createState() => _MessagesListState();
}
class _MessagesListState extends State<MessagesList> {
Future<List<Message>> future;
List<Message> messages = [];
initState() {
super.initState();
fetch();
}
void fetch() async {
future = Message.browse();
messages = await future;
print(messages.length);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
IconButton(
icon: Icon(Icons.refresh_sharp),
onPressed: () async {
var _messages = await Message.browse();
setState(() {
messages = _messages;
});
})
],
),
body: FutureBuilder(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError)
return Text("There was an error: ${snapshot.error}");
var messages = snapshot.data;
return ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
Message message = messages[index];
return ListTile(
title: Text(message.subject),
isThreeLine: true,
leading: CircleAvatar(
child: Text("PJ"),
backgroundColor: Colors.red[500],
foregroundColor: Colors.black,
),
subtitle: Text(
message.body,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
MessageDeatail(message.subject, message.body),
),
);
},
);
},
);
}
},
),
floatingActionButton: ComposeButton(messages),
);
}
}
ComposeButton.dart
import 'package:flutter/material.dart';
import 'Message.dart';
import 'MessageCompose.dart';
class ComposeButton extends StatelessWidget {
final List<Message> messages;
ComposeButton(this.messages);
#override
Widget build(BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
Message message = await Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => MessageCompose(),
),
);
if (message != null) {
print(messages.length);
messages.add(message);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
"Your message has been sent",
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.grey,
),
);
}
},
);
}
}
I watch a tutorial, and there code works
ps. i am sorry for my english
I think that your ComposeButton(messages) runs before messages are fetched. Body of scaffold is initialized as FutureBuilder (which goes async) and then ComposeButton(messages) runs. But future is still not completed so 'messages' is still an empty list.
If I am right then answer to your trouble is in top comment of tutorial you posted.

Search Listview In Flutter

I have searched the web on how to add search functionality to my Flutter application. I have a list view that gets data from a mysql database and pass it as a json string. I want to add a search box on top of the list, when the user enters a string, it will take the string and search the name field of the list and re-populate the list. Here is my code please.
Thank you.
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
Future<List<Attendants>> students;
final studentListKey = GlobalKey<HomeState>();
#override
void initState() {
super.initState();
students = getStudentList();
}
Future<List<Attendants>> getStudentList() async {
final response = await http.get("${Env.URL_PREFIX}/list.php");
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Attendants> students = items.map<Attendants>((json) {
return Attendants.fromJson(json);
}).toList();
return students;
}
void refreshStudentList() {
setState(() {
students = getStudentList();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: studentListKey,
appBar: AppBar(
title: Text('Members List'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search,
color: Colors.white,),
onPressed: null),
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
refreshStudentList();
},
)
],
),
body: Center(
child: FutureBuilder<List<Attendants>>(
future: students,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// By default, show a loading spinner.
if (!snapshot.hasData) return CircularProgressIndicator();
// Render student lists
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
var data = snapshot.data[index];
return Card(
child: ListTile(
leading: Icon(Icons.person),
trailing: Icon(Icons.view_list),
title: Text(
data.name,
style: TextStyle(fontSize: 20),
),
onTap: () {
Navigator.push(
context,
EnterExitRoute(
exitPage: Home(),
enterPage: Details(attendants: data),
),
);
},
),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return Create();
}));
},
),
);
}
}
Let me know if my question is not clear so I can explain it further
If you get all objects about your search in the future function
add your code :
// By default, show a loading spinner.
if (!snapshot.hasData) return CircularProgressIndicator();
var list = snapshot.data.where((student){
// Or some other query..
return student[“some”].contains(searchTerm);
}).toList();
return ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
var data = list[index];
..............
Else if not, this is a different subject in Flutter.

Flutter: Maintain state when navigating to different page

I have the following code which creates a Listview and adds an icon to the end of each item. This icon can be tapped in order to highlight it.
The problem is, when I navigate away from the page the items are deselected. I am using Navigator.of to load the Listview:
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return new LikedList();
}));
Full code:
import 'package:flutter/material.dart';
import './images.dart';
class LikedList extends StatefulWidget {
#override
_LikedListState createState() => _LikedListState();
}
class _LikedListState extends State<LikedList> {
List<bool> _likes = List.filled(ImagesState.likes.length,true);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Liked List'),
),
body: ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: ImagesState.likes.length,
itemBuilder: (context, index) {
final item = ImagesState.likes[index];
return ListTile(
title: Text(item),
trailing: IconButton(
icon: _likes[index]
? Icon(
Icons.favorite_border,
color: Colors.grey,
)
: Icon(
Icons.favorite,
color: Colors.red,
),
onPressed: () {
setState(() {
print(_likes);
_likes[index] = !_likes[index];
print(_likes);
});
},
),
onLongPress: () {
setState(() {
print(ImagesState.likes[index]);
ImagesState.likes.removeAt(index);
});
},
);
},
),
);
}
}

How to rebuild only modified item in list with ChangeNotifier

I am trying to fetch the data from server. When the button is pushed, a dummy widget is added to the list and response is shown after data is fetched.
I called notifyListeners() when item is added to the list and when data is loaded, but all of items are rebuilt even unchanged items.
How can I prevent rebuilding unchanged item?
Here's my code.
class Item {
bool isLoaded;
String request;
String data;
Item(this.request) : isLoaded = false;
Future loadItemData() {
// dummy for api request
return Future.delayed(Duration(seconds: 3)).whenComplete(() {
data = "item get result";
isLoaded = true;
});
}
}
class ItemList extends ChangeNotifier {
List<Item> lists = [];
void addItem(String request) {
var item = Item(request);
lists.add(item);
item.loadItemData().whenComplete(() {
notifyListeners();
});
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ItemList(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyItems(),
),
);
}
}
class MyItems extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Items"),
),
body: Consumer<ItemList>(
builder: (context, value, child) {
return Column(
children: <Widget>[
RaisedButton(
child: const Text("Add Item"),
onPressed: () {
value.addItem("dummy request id");
},
),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
var item = value.lists[index];
return item.isLoaded
? ListTile(
title: Text(value.lists[index].data),
)
: ListTile(
leading: CircularProgressIndicator(),
);
},
itemCount: value.lists.length,
),
),
],
);
},
),
);
}
}
Make use of Unique keys with ListTile.
ListTile(
key: ValueKey(value.lists[index].data['id']),
...
)