I am trying to build a Reviews section for my app. In that, I'm trying to have a read more feature which obviously as the name suggests will expand the widget to show more text, upon tapping the widget.
I'm using a indexed stack with BottomNavigationBar to switch between reviews and the product page.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'Product_Page_Widgets.dart';
void main() => runApp(Product());
class Product extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ProductState(),
);
}
}
class ProductState extends StatefulWidget {
Map<dynamic,dynamic> productData;
CollectionReference reviews;
ProductState({#required this.productData,#required this.reviews});
#override
_ProductStateState createState() => _ProductStateState();
}
class _ProductStateState extends State<ProductState> with TickerProviderStateMixin{
List<NetworkImage> _Images = [];
List<Widget> productImages = [];
List<Widget> reviewWidgets = [];
String changeableText = "";
List<String> firstHalf = [];
List<String> secondHalf = [];
var cardHeight = [];
bool load = false;
int pageIndex = 0;
List<Widget> pageWidget = [];
addPageWidgets() async{
pageWidget.add( ProductPageWidget(
productName: widget.productData['Name'],
galleryItems: _Images,cachedImages: productImages,description:widget.productData['description'] ?? "placeholder")
);
Widget w = await buildReviewsWidgets();
pageWidget.add(w);
}
GlobalKey key = new GlobalKey();
Future <Widget> buildReviewsWidgets() async{
var docs = await widget.reviews.getDocuments();
var documents = docs.documents;
print(documents.toString());
if(docs != null) {
for (int i = 0; i < documents.length; i++) {
cardHeight.add(MediaQuery.of(context).size.height/4);
print(documents[i].data['Review']);
if(documents[i].data['Review'].toString().length>50){
firstHalf.add( documents[i].data['Review'].toString().substring(0,50));
secondHalf.add(documents[i].data['Review'].toString().substring(51));
}else{
firstHalf.add( documents[i].data['Review'].toString());
secondHalf.add("");
}
}
}
for(var doc in documents){
print(doc.data);
}
return Container(
height:MediaQuery.of(context).size.height,
child: ListView.builder(
key: key,
itemCount: documents.length,
itemBuilder: (context,index) => AnimatedSize(
vsync: this,
curve: Curves.easeIn,
duration: new Duration(milliseconds: 300),
child: Container(
height: cardHeight[index],
child: Card(
elevation: 2.0,
child: ListTile(
onTap: (){
stateUpdate(index);
},
leading: CircleAvatar(),
title: Text(documents[index].data['Title']),
subtitle: Column(children: [
RatingBar(
itemBuilder: (context, _) => Icon(
Icons.star,
color: Colors.amber,
),
maxRating: 5.0,
allowHalfRating: false,
ignoreGestures: true,
initialRating: int.parse(documents[index].data['Rating'].toString()).toDouble(),
),
Wrap(
children: <Widget>[
Text(firstHalf[index].toString()),
Text("Read More")
],
),
]),
),
),
),
),
),
);
}
void AddNetworkImages (BuildContext context){
if(!load) {
for (int i = 0; i < widget.productData.length; i++) {
if (widget.productData.containsKey('Img$i')) {
print("Img$i exists");
_Images.add(
NetworkImage(widget.productData['Img$i'])
);
productImages.add(
CachedNetworkImage(
imageUrl: widget.productData['Img$i'],
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
);
}
}
}
}
void loadWidgets(BuildContext context){
setState(() {
});
load = true;
}
#override
void initState() {
//AddNetworkImages(context);
addPageWidgets();
super.initState();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => AddNetworkImages(context));
WidgetsBinding.instance
.addPostFrameCallback((_) => loadWidgets(context));
return Scaffold(
appBar: AppBar(
actions: <Widget>[
],
title: Text("$changeableText"),
backgroundColor: Colors.deepOrangeAccent,
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(
),
child:IndexedStack(
index: pageIndex,
children: pageWidget,
)),
bottomNavigationBar:BottomNavigationBar(
currentIndex: pageIndex,
onTap: (index){
setState(() {
pageIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text("Overview")
),
BottomNavigationBarItem(
icon: Icon(Icons.star),
title: Text("Reviews")
)
],
)
);
}
void stateUpdate(int index) {
setState(() {
firstHalf[index]+=secondHalf[index];
cardHeight[index] = MediaQuery.of(context).size.height/2;
});
print(index);
print(firstHalf[index]);
}
}
Now, inside the buildReviewsWidgets() method, i'm getting all of the review data ,iterating over it and splitting it into the firstHalf and secondHalf lists, so that, on tapping the ListTile, i can simply join firstHalf[index] and secondHalf[index], as i have done so on the ontap method of the ListTiles, but tapping on the list tile does nothing at all..the list tiles don't update, basically nothing happens. But whats interesting is that if I setState and change the value of changeableText, it does update the text on the appbar.. can anyone tell me why this is happening? how do i fix it?
I just cleaned the code up and removed the need of storing widgets with lists..not involving any lists with the widgets instantly fixed the problem.
Related
I have two separate widgets. I want to update the child widget textFormField value when I click on the button in the parent widget.
I have provided the code below. How can I do this without getX or Provider in flutter? I looked for a solution to this problem but did not find a solution for this kind of problem.
Parent Widget
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['labour'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return LabourAttendanceWidget(
workerId: data[index]['worker_id'],
masterAttendanceId: widget.attendanceId,
name: data[index]['worker_name'],
wages: data[index]['attendance_worker_wages'],
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());
}
},
),
CHILD WIDGET
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:get/get.dart';
import 'package:site_management/supervisors/screens/supervisor_attendance/controller/labour_attendance_controller.dart';
import 'package:site_management/supervisors/supervisor_services/supervisor_attendance_services.dart';
class LabourAttendanceWidget extends StatefulWidget {
const LabourAttendanceWidget({
Key? key,
required this.name,
required this.wages,
required this.isPrensent,
required this.workerId,
required this.masterAttendanceId,
}) : super(key: key);
final int workerId;
final int masterAttendanceId;
final String name;
final String wages;
final String isPrensent;
#override
State<LabourAttendanceWidget> createState() => _LabourAttendanceWidgetState();
}
class _LabourAttendanceWidgetState extends State<LabourAttendanceWidget> {
final TextEditingController _wagesController = TextEditingController();
String _character = "";
Timer? searchOnStoppedTyping;
LabourAttendanceController attendanceController =
Get.put(LabourAttendanceController());
_onChangeHandler(value) {
const duration = Duration(
milliseconds:
800); // set the duration that you want call search() after that.
if (searchOnStoppedTyping != null) {
setState(() => searchOnStoppedTyping?.cancel()); // clear timer
}
setState(() =>
searchOnStoppedTyping = Timer(duration, () => submitWages(value)));
}
submitWages(value) {
SupervisorAttendanceServices.storeWorkerWages(
widget.workerId, value, widget.masterAttendanceId);
}
#override
void initState() {
super.initState();
_character = widget.isPrensent;
_wagesController.text = widget.wages;
setState(() {});
}
#override
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.spaceAround,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
controller: _wagesController,
onChanged: _onChangeHandler,
decoration: const InputDecoration(
// border: OutlineInputBorder(),
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
Row(
children: [
Radio(
value: "P",
groupValue: _character,
fillColor:
MaterialStateColor.resolveWith((states) => Colors.green),
onChanged: (selectedValue) {
setState(() {
_character = selectedValue.toString();
SupervisorAttendanceServices.changeAttendance(
widget.workerId,
_character,
widget.masterAttendanceId)
.then((response) {
if (response == 1) {
return null;
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Row(
children: const [
Icon(FeatherIcons.home),
SizedBox(
width: 10,
),
Text("Something went wrong !"),
],
),
),
// sb
);
}
});
});
attendanceController
.getAttendanceCount(widget.masterAttendanceId);
},
),
const Text("P"),
Radio(
value: "A",
fillColor:
MaterialStateColor.resolveWith((states) => Colors.red),
groupValue: _character,
onChanged: (selectedValue) {
setState(() {
_wagesController.text = "0";
_onChangeHandler("0");
_character = selectedValue.toString();
SupervisorAttendanceServices.changeAttendance(
widget.workerId,
_character,
widget.masterAttendanceId);
});
attendanceController
.getAttendanceCount(widget.masterAttendanceId);
}),
const Text("A"),
],
)
],
)
]),
);
}
}
First change your LabourAttendanceWidget to this:
class LabourAttendanceWidget extends StatefulWidget {
const LabourAttendanceWidget({
Key? key,
required this.name,
required this.wages,
required this.isPrensent,
required this.workerId,
required this.masterAttendanceId,
this.someString,
}) : super(key: key);
final int workerId;
final int masterAttendanceId;
final String name;
final String wages;
final String isPrensent;
final String someString;
#override
State<LabourAttendanceWidget> createState() => _LabourAttendanceWidgetState();
}
then in LabourAttendanceWidget's initState do this:
#override
void initState() {
super.initState();
_character = widget.isPrensent;
_wagesController.text = widget.someString ?? widget.wages;
setState(() {});
}
and in your parent widget first define this variable out of build method:
String? _value;
then do this:
return LabourAttendanceWidget(
workerId: data[index]['worker_id'],
masterAttendanceId: widget.attendanceId,
name: data[index]['worker_name'],
wages: data[index]['attendance_worker_wages'],
someString: _value,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
then fill _value when came back from pop up and then call setstate.
I'm trying to implement a search bar in my app that displays songs, which the user is then able to search through to find a specific song. I pass the List songs into my CustomSearchDelegate class, but I can't access the variable songTitle from the for loop in buildResults and buildSuggestions. Relevant code below:
import 'package:flutter/material.dart';
class Recordings extends StatelessWidget {
Recordings({Key? key, required this.title}) : super(key: key);
final String title;
//This will be replaced by data from the database
final List<Widget> songs = [
const Song(songTitle: "September"),
const Song(songTitle: "Don't Stop Me Now"),
const Song(songTitle: "Let It Go"),
const Song(songTitle: "Smoke on the Water"),
const Song(songTitle: "Don't Kill My Vibe"),
const Song(songTitle: "Mamma Mia"),
const Song(songTitle: "4'33"),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: [
IconButton(
onPressed: () {
showSearch(context: context, delegate: CustomSearchDelegate(searchTerms: songs));
},
icon: const Icon(Icons.search)),
],
),
body: Container(
color: Colors.grey[600],
child: Center(
child: ListView(
children: songs,
),
),
),
);
}
}
class Song extends StatelessWidget {
const Song({Key? key, required this.songTitle}) : super(key: key);
final String songTitle;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Container(
alignment: Alignment.center,
color: Colors.lightBlue[300],
child: Text(
songTitle,
style: const TextStyle(color: Colors.black),
),
),
);
}
}
class CustomSearchDelegate extends SearchDelegate {
CustomSearchDelegate({required this.searchTerms});
final List<Widget> searchTerms;
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
query = "";
},
icon: const Icon(Icons.clear),
),
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
#override
Widget buildResults(BuildContext context) {
List<Widget> matchQuery = [];
//looping through each item in searchTerms
for (var song in searchTerms) {
if (song.title.toLowerCase().contains(query.toLowerCase())) { //ERROR OCCURS HERE
matchQuery.add(song);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: ((context, index) {
var result = matchQuery[index].title;
return ListTile(
title: Text(result),
);
}),
);
}
#override
Widget buildSuggestions(BuildContext context) {
List<Widget> matchQuery = [];
//looping through each item in searchTerms
for (var song in searchTerms) {
if (song.title.toLowerCase().contains(query.toLowerCase())) { //ERROR OCCURS HERE
matchQuery.add(song);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: ((context, index) {
var result = matchQuery[index].title;
return ListTile(
title: Text(result),
);
}),
);
}
}
Figured out how to fix this. I changed List<Widget> to List<Song> wherever I needed to access songTitle. I also added a getSongTitle to the Song class that returned songTitle.
I need to generate an N amount of DropDownButtons fetched from FireBase when I click on a button for example. I have this code where I generate the N amount but the variables are the same for each one of them, the ideal would be to store each DropDownButton with an independent variable.
import 'package:flutter/material.dart';
class Formulario2Screen extends StatefulWidget {
const Formulario2Screen({Key? key}) : super(key: key);
#override
State<Formulario2Screen> createState() => _Formulario2ScreenState();
}
List<Widget> bodyElements = [];
int num = 0;
var selecCurrency, eleccion, selecCurrency2, eleccion2;
class _Formulario2ScreenState extends State<Formulario2Screen> {
bool pressed = false;
void addBodyElement() {
bodyElements.add(
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('Sintomas').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Text('Loading');
} else {
List<DropdownMenuItem> currencyItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
DocumentSnapshot snap = snapshot.data.docs[i];
currencyItems.add(
DropdownMenuItem(
child: Text(
snap.id,
),
value: snap.id,
),
);
eleccion = currencyItems;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton<dynamic>(
items: eleccion,
onChanged: (currencyValue) {
var snackBar =
SnackBar(content: Text('Se seleccionó $currencyValue'));
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(snackBar);
setState(() {
selecCurrency = currencyValue;
});
},
value: selecCurrency,
hint: const Text('Selecciona un sintoma')),
],
);
},
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Center(child: Text('Home')),
brightness: Brightness.dark,
leading: IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
bodyElements.clear();
num = 0;
});
},
),
),
body: ListView(
children: <Widget>[
Column(
children: bodyElements,
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.add),
label: Text('Add'),
onPressed: () {
num++;
setState(() {
addBodyElement();
});
},
),
);
}
}
Basically what I do is every time I press the Add button, the previously created widget is added.
I'm currently working on a Flutter mobile app which is supposed to work on Android and IOS.
The issue I'm having is about ListView and updating it.
I know I'm doing wrong with a lot of things but I'm learning and I would like to learn properly. So if you have any comments, tips about the code pls give them :)
Basically here is what it does :
The main player will choose who will play with him at the game via an AlertDialog which has a CheckboxList inside it and every time he selects a player, it will update a list called choosenPlayers which has all Player objects choosen in it.
Then what I want to do is to display a list of all selected players (a reorderable list to change the order of players) and update it everytime the choosenPlayers list is updated.
I managed to display these players but I have to reload the page by going in the drawer menu and clicking on page link to see added players.
I use a stateful widget for my players reorderable list and I pass to the parent the list of players (This is not the rigth way to do it I know) :
import 'package:flutter/material.dart';
import 'package:mollky/models/player.dart';
class ChoosenPlayers extends StatefulWidget {
_ChoosenPlayersState _choosenPlayersState = _ChoosenPlayersState();
List<Player> choosenPlayers = [];
ChoosenPlayers({Key key, this.choosenPlayers}) : super(key: key);
#override
_ChoosenPlayersState createState() => _choosenPlayersState;
}
class _ChoosenPlayersState extends State<ChoosenPlayers> {
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return ReorderableListView(
onReorder: onReorder,
children: getListItems(),
);
}
List<ListTile> getListItems() => widget.choosenPlayers
.asMap()
.map((i, item) => MapEntry(i, buildTenableListTile(item, i)))
.values
.toList();
ListTile buildTenableListTile(Player item, int index) {
return ListTile(
key: ValueKey(item.id),
title: Text(item.nickname + " " + item.name),
leading: Text("#${index + 1}"),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
Player reOrderedPlayer = widget.choosenPlayers[oldIndex];
widget.choosenPlayers.removeAt(oldIndex);
widget.choosenPlayers.insert(newIndex, reOrderedPlayer);
});
}
}
Here is the code of the main page where reorderable list is displayed and AlertDialog showed.
Sorry, couldn't format with Dart, don't run the code snipped obviously xD
class NewGame extends StatefulWidget {
#override
State<StatefulWidget> createState() => NewGameState();
}
class NewGameState extends State<NewGame> {
List<Player> players = [];
NewGameState() {
this.players.add(
Player(id: 0, name: "Dupont", nickname: "julien", picture: "test"));
this
.players
.add(Player(id: 1, name: "Dpont", nickname: "julien", picture: "test"));
this
.players
.add(Player(id: 2, name: "Dunt", nickname: "juen", picture: "test"));
}
static List<Player> _choosenPlayers = [];
ChoosenPlayers choosenPlayersObject = ChoosenPlayers(
choosenPlayers: _choosenPlayers,
);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerWidget(),
appBar: AppBar(title: Text("Nouvelle partie")),
body: Column(children: <Widget>[
Card(
child: ListTile(
leading: Icon(Icons.people),
title: Text("Choisissez les joueurs"),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Les joueurs existants"),
content:
Stack(overflow: Overflow.visible, children: <
Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.lightBlue,
),
),
),
Positioned(
child: StatefulBuilder(
builder: (BuildContext context,
StateSetter setState) {
return Container(
width: 350.0,
height: 150.0,
child: ListView.builder(
itemCount: players.length,
itemBuilder:
(context, playerIndex) {
return CheckboxListTile(
title: Text(players[playerIndex]
.nickname +
" " +
players[playerIndex].name),
value: _choosenPlayers.contains(
players[playerIndex]),
onChanged: (bool value) {
if (!_choosenPlayers.contains(
players[playerIndex])) {
_choosenPlayers.add(
players[playerIndex]);
setState(() {});
} else {
_choosenPlayers.remove(
players[playerIndex]);
setState(() {});
}
},
secondary: const Icon(
Icons.hourglass_empty),
);
}),
);
},
),
),
]));
});
})),
Container(
width: 350.0,
height: 150.0,
child: choosenPlayersObject,
),
]));
}
}
I've seen nothing on forums about updating list without triggering a callback like onRefresh which is not what I want.
It is a real nightmare xD. Sorry for french words btw I can translate if needed but they are not important, simple text.
Here are two screenshots of the list and alert dialog :
Thank you in advance :)
The state of parent widget is not updated. That's why, even though the payer is added to the list. But not shown to in parent widget.
The setState you called only update the state of StatefulBuilder not of the NewGame.
Check out the below code.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NewGameScreen(),
);
}
}
class NewGameScreen extends StatefulWidget {
#override
_NewGameScreenState createState() => _NewGameScreenState();
}
class _NewGameScreenState extends State<NewGameScreen> {
List<Player> _availablePlayers = [];
List<Player> _selectedPlayers = [];
#override
void initState() {
super.initState();
_availablePlayers = [
Player(id: 0, name: "Ross", nickname: "Geller", picture: "test"),
Player(id: 1, name: "Rachel", nickname: "Green", picture: "test"),
Player(id: 2, name: "Chandler", nickname: "Bing", picture: "test"),
];
}
_selectPlayer() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Existing players"),
content: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.lightBlue,
),
),
),
StatefulBuilder(
builder: (BuildContext context, StateSetter alertState) {
return Container(
width: 350.0,
height: 150.0,
child: ListView.builder(
itemCount: _availablePlayers.length,
itemBuilder: (context, playerIndex) {
return CheckboxListTile(
title:
Text(_availablePlayers[playerIndex].nickname + " " + _availablePlayers[playerIndex].name),
value: _selectedPlayers.contains(_availablePlayers[playerIndex]),
onChanged: (bool value) {
if (_selectedPlayers.contains(_availablePlayers[playerIndex])) {
_selectedPlayers.remove(_availablePlayers[playerIndex]);
} else {
_selectedPlayers.add(_availablePlayers[playerIndex]);
}
setState(() {});//ALSO UPDATE THE PARENT STATE
alertState(() {});
},
secondary: const Icon(Icons.hourglass_empty),
);
},
),
);
},
),
],
),
);
},
);
}
_onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
print('oldIndex:$oldIndex');
print('newIndex:$newIndex');
setState(() {
Player player = _selectedPlayers[newIndex];
_selectedPlayers[newIndex] = _selectedPlayers[oldIndex];
_selectedPlayers[oldIndex] = player;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("New Game")),
body: Column(
children: <Widget>[
Card(
child: ListTile(
leading: Icon(Icons.people),
title: Text("Choose players"),
onTap: _selectPlayer,
),
),
Flexible(
child: ReorderableListView(
onReorder: _onReorder,
children: _selectedPlayers.map((player) {
return ListTile(
key: ValueKey(player.id),
title: Text(player.nickname + " " + player.name),
leading: Text("#${_selectedPlayers.indexOf(player) + 1}"),
);
}).toList(),
),
),
],
),
);
}
}
class Player {
int id;
String name;
String nickname;
String picture;
Player({this.id, this.name, this.nickname, this.picture});
}
Hope it helps :)
I'm getting started with flutter/dart and I'm trying to implement a simple note app using InheritedWidget and TextControllers, but when I add or edit some note it doesn't update the main screen. I printed the new notes list in console and it is updated with the addings and editings but is not updated in main screen, still showing the initial note list ({'title': 'someTitle1', 'text': 'someText1'}, ...).
main.dart :
void main() => runApp(NoteInheritedWidget(
MaterialApp(
title: 'Notes App',
home: HomeList(),
),
));
home screen scaffold body :
List<Map<String, String>> get _notes => NoteInheritedWidget.of(context).notes;
...
body: ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.symmetric(vertical: 5, horizontal: 7),
child: ListTile(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NotePage(noteMode: NoteMode.Editing, index: index))
);
print(_notes);
},
trailing: Icon(Icons.more_vert),
title: _NoteTitle(_notes[index]['title']),
subtitle: _NoteText(_notes[index]['text']),
),
);
},
),
Add/Edit Note page :
enum NoteMode {
Adding,
Editing
}
class NotePage extends StatefulWidget {
final NoteMode noteMode;
final int index;
const NotePage ({this.noteMode, this.index});
#override
_NotePageState createState() => _NotePageState();
}
class _NotePageState extends State<NotePage> {
final TextEditingController _titleController = TextEditingController();
final TextEditingController _textController = TextEditingController();
List<Map<String, String>> get _notes => NoteInheritedWidget.of(context).notes;
#override
void didChangeDependencies() {
if (widget.noteMode == NoteMode.Editing) {
_titleController.text = _notes[widget.index]['text'];
_textController.text = _notes[widget.index]['title'];
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.noteMode == NoteMode.Adding ? 'Add Note' : 'Edit Note'
),
centerTitle: true,
backgroundColor: Colors.indigo[700],
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextField(
controller: _titleController,
decoration: InputDecoration(
hintText: 'Note Title',
border: OutlineInputBorder(),
),
),
SizedBox(height: 20),
TextField(
controller: _textController,
maxLines: 20,
decoration: InputDecoration(
hintText: 'Note Text',
border: OutlineInputBorder(),
),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
_NoteButton(Icons.save, 'Save', () {
final title = _titleController.text;
final text = _textController.text;
if (widget.noteMode == NoteMode.Adding) {
_notes.add({'title': title, 'text': text});
print(_notes);
} else if (widget.noteMode == NoteMode.Editing) {
_notes[widget.index] = {'title': title, 'text': text};
print(_notes);
}
Navigator.pop(context);
}),
_NoteButton(Icons.clear, 'Discard', () {
Navigator.pop(context);
}),
if (widget.noteMode == NoteMode.Editing)
_NoteButton(Icons.delete, 'Delete', () {
_notes.removeAt(widget.index);
Navigator.pop(context);
}),
],
),
],
),
),
),
);
}
}
InheritedWidget :
class NoteInheritedWidget extends InheritedWidget {
final notes = [
{'title': 'someTitle1', 'text': 'someText1'},
{'title': 'someTitle2', 'text': 'someText2'},
{'title': 'someTitle3', 'text': 'someText3'}
];
NoteInheritedWidget(Widget child) : super(child: child);
static NoteInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<NoteInheritedWidget>();
}
#override
bool updateShouldNotify(NoteInheritedWidget old) {
return old.notes != notes;
}
}
Home screen after add a note :
HomeScreen
List of notes printed in console after add a note :
I/flutter (18079): [{title: someTitle1, text: someText1}, {title: someTitle2, text: someText2}, {title: someTitle3, text: someText3}, {title: NewAddNoteTitle, text: NewAddNoteText}]
I'm using Android Studio and a real device instead an emulator.
I can't find the error and if you have another way to do this 'update' please show me.
I found a solution using the onPressed method as async and then an empty setState, is there any problem for the code doing this?
code:
child: ListTile(
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => NotePage(noteMode: NoteMode.Editing, index: index))
);
setState(() {});
print(_notes);
},
...
floatingActionButton: FloatingActionButton(
onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => NotePage(noteMode: NoteMode.Adding))
);
setState(() {});
print(_notes.length);
print(_notes);
},