I am trying to navigate to a new page newData[index]["title"] when tap on listTile, how can I access newData, index and data variables out of this scope, I know I have to declare them in global scope but I tried this.index, but it doesn't work, I created a _onTap() method but I don't have access to index so I can only access the by asking for a specific position [0] for instance.
class _ContentPageState extends State<ContentPage> {
#override
Widget build(BuildContext context,) {
List data;
return new Scaffold(
appBar: new AppBar(
title: new Text("Local json file"),
),
body: new Container(
child: new Center(
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('data_files/file.json'),
builder: (context, snapshot) {
var newData = JSON.decode(snapshot.data.toString());
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new ListTile(
title: new Text(newData[index]['title'],textScaleFactor: 1.5),
),
);
},
itemCount: newData == null ? 0 : newData.length,
);
}),
),
));
}
}
I guess you're trying to have a private _onTap method inside your class similar to :
_onTap(newData, int index) {
}
That's pretty simple, inside your ListTile you can wrap your _onTap inside another function like this :
return new Card(
child: new ListTile(
onTap: () => _onTap(newData, index),
title: new Text(newData[index]['title'], textScaleFactor: 1.5),
),
);
the previous answer is passing newData as argument. Another alternative is declare newData outside the build method.
class _ContentPageState extends State<ContentPage> {
var newData;// << here >> after class
#override
Widget build(BuildContext context,) {
So, in the assign remove de type (var)
newData = JSON.decode(snapshot.data.toString());
Related
I need to refresh my UI when data changes. I have a ListView to display Cards that contain my events, and these events are sorted with a datepicker. When I change the date with the datepicker I need to reload the page to display the correct pages.
I try to pass the datepicker data as a parameter of the ListView to sort the events in the ListView, I also tried to sort the data before ListView is built with a parameter containing the list of sorted data.
Widget of my HomePage class :
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: Image.asset('assets/logo2.PNG', fit: BoxFit.contain),
title: Text(widget.title,style: TextStyle(fontFamily: 'IndieFlower',fontSize: 30,fontWeight: FontWeight.bold),),
actions: <Widget>[ // Add 3 lines from here...
new IconButton(icon: const Icon(Icons.account_circle, color: Color(0xFFf50057)), onPressed: _pushSaved, iconSize: 35,),
], // ... to here.
centerTitle: true,
backgroundColor: new Color(0xFF263238),
),
body: FutureBuilder<List<Event>>(
future: fetchPosts(http.Client()),
builder: (context, snapshot) {
//print(convertIntoMap(snapshot.data));
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListViewEvents(posts: sortEvents(snapshot.data), pickerDate: '${dobKey.currentState.dobDate} ' +dobKey.currentState.dobStrMonth +' ${dobKey.currentState.dobYear}')
: Center(child: CircularProgressIndicator(backgroundColor: Color(0xFFf50057),));
},
),
bottomNavigationBar : BottomAppBar(
child: Container(height: 100.0,
alignment: Alignment.topCenter,
child:
DatePicker(
key: dobKey,
setDate: _setDateOfBirth,
customItemColor: Color(0xFFf50057),
customGradient:
LinearGradient(begin: Alignment(-0.5, 2.8), colors: [
Color(0xFFf50057),
Color(0xFFffcece),
Color(0xFFf50057),
]),
),
),
),
);
}
}
This is my map:
List<Event> sortEvents(List<Event> data) {
List<Event> eventsSelected = new List<Event>();
for(var index = 0; index < data.length; index++){
if (data[index].date ==
//callback of datepicker
'${dobKey.currentState.dobYear}-${dobKey.currentState.month}-
${dobKey.currentState.dobDate}') {
eventsSelected.add(data[index]);
}
}
return eventsSelected;
}
And this is how I render my cards:
class ListViewEvents extends StatefulWidget {
ListViewEvents({Key key, this.posts, this.pickerDate}) : super(key: key);
final posts;
final pickerDate;
#override
_ListViewEventsState createState() => _ListViewEventsState();
}
class _ListViewEventsState extends State<ListViewEvents> with
SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
if(widget.posts.isEmpty) {
return Center(
child: Text(
'No events for this date'
),
);
}
return ListView.builder(
itemCount: widget.posts.length,
padding: const EdgeInsets.all(15.0),
itemBuilder: (context, index) {
return Center(
child: Text(
'Title : ${widget.posts[index].title}'
),
);
},
);
}
}
I actually have a system to display my events's Cards that works but it's not in real-time, I would like to refresh the UI when the data of the datepicker changes.
You need to call setState() when list data changes.
for(var index = 0; index < data.length; index++){
if (data[index].date ==
//callback of datepicker
'${dobKey.currentState.dobYear}-${dobKey.currentState.month}-
${dobKey.currentState.dobDate}') {
setState(() { eventsSelected.add(data[index]); } ); <--- add it here.
}
}
you can use setState() for this problem so setState() call when anything change in the screen
I am currently using StreamBuilder to get data from Firestore and so far, it is working good.
I currently want to perform some async operations to the data before displaying.
The code to get the data is below.
List<Model> listToDisplay = new List<Model>();
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: topBar,
body: StreamBuilder(
stream: Firestore.instance.collection('/myPath').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.connectionState == ConnectionState.active) {
listToDisplay.clear();
for (DocumentSnapshot _snap in snapshot.data.documents) {
Model _add = new Model.from(_snap);
listToDisplay.add(_add);
}
return TabBarView(
children: <Widget>[
ListView.builder(
itemCount: mouveList.length,
itemBuilder: (context, index) {
return Card(listToDisplay[index]);
},
),
Icon(Icons.directions_transit),
],
);
} else {
return Container(
child: Center(child: CircularProgressIndicator()));
}
})));
I tried adding the async operation in the for in loop but that did not work, it did not wait for it. Also, add await did not work because Widget build(BuildContext context) cannot be async.
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: topBar,
body: StreamBuilder(
stream: Firestore.instance.collection('/myPath').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.connectionState == ConnectionState.active) {
listToDisplay.clear();
for (DocumentSnapshot _snap in snapshot.data.documents) {
Model _add = new Model.from(_snap);
//Added
//_add.getCalculate(); <------- Async function
_add.Calculate(); <------ Flutter does not wait for this
await _add.Calculate(); <------ Produces an error
listToDisplay.add(_add);
}
return TabBarView(
children: <Widget>[
ListView.builder(
itemCount: mouveList.length,
itemBuilder: (context, index) {
return Card(listToDisplay[index]);
},
),
Icon(Icons.directions_transit),
],
);
} else {
return Container(
child: Center(child: CircularProgressIndicator()));
}
})));
Any ideas on how to get data as stream, perform operations on the data before displaying the data all using StreamBuilder and ListViewBuilder ?
I'm currently iterating the data from a StreamBuilder in corresponding lists and then using a ListView.builder to display each data item from List.count. The code begins with these public/file Lists...
List names = new List();
List ids = new List();
List vidImages = new List();
List numbers = new List();
Then this in my Stateful Widget Builder...
child: new StreamBuilder(
stream:
fb.child('child').orderByChild('value').onValue,
builder:
(BuildContext context, AsyncSnapshot<Event> event) {
if (event.data?.snapshot?.value == null) {
return new Card(
child: new Text(
'Network Error, Please Try again...',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
} else if (event.data?.snapshot?.value != null) {
Map myMap =
event.data?.snapshot?.value; //store each map
var titles = myMap.values;
List onesTitles = new List();
List onesIds = new List();
List onesImages = new List();
List onesRank = new List();
List<Top> videos = new List();
for (var items in titles) {
var top = new Top(
videoId: items['vidId'],
rank: items['Value'],
title: items['vidTitle'],
imageString: items['vidImage']);
videos.add(top);
videos..sort((a, b) => b.rank.compareTo(a.rank));
}
for (var vids in videos) {
onesTitles.add(vids.title);
onesIds.add(vids.videoId);
onesImages.add(vids.imageString);
onesRank.add(vids.rank);
}
names = onesTitles;
ids = onesIds;
numbers = onesRank;
vidImages = onesImages;
switch (event.connectionState) {
case ConnectionState.waiting:
return new Card(
child: new Text('Loading...',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
else
return new InkWell( child: new ListView.builder(
itemCount:
names == null ? 0 : names.length,
itemBuilder:
(BuildContext context, int index) {
return new Card( child: new Text(names[index]))
How would I properly access the _runThisFunction(...) within the onTap()?
...
class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
void _runThisFunction() async {
print('Run me')
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: _loadingDeals,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState == ConnectionState.done
? RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data['deals'].length,
itemBuilder: (context, index) {
final Map deal = snapshot.data['deals'][index];
return _getDealItem(deal, context);
},
),
)
: Center(
child: CircularProgressIndicator(),
);
},
);
}
}
Container _getDealItem(Map deal, context) {
return new Container(
height: 90.0,
child: Material(
child: InkWell(
child: _getDealRow(deal), // <-- this renders the row with the `deal` object
onTap: () {
// Below call fails
// 'The function isn't defined'
_runThisFunction();
},
),
),
);
}
The reason for that is that you are out of scope.
Little hint: The word "function" always indicates that the function you are trying to call is not part of a class and the keyword "method" shows you that the function you are trying to call is part of a class.
In your case, _runThisFunction is defined inside of _DealList, but you are trying to call it from outside.
You either need to move _getDealItem into _DealList or _runThisFunction out.
/// In this case both methods [_runThisFunction()] and [_getDealItem()] are defined inside [_DealList].
class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
void _runThisFunction() ...
Container _getDealItem() ...
}
/// In this case both functions are defined globally.
void _runThisFunction() ...
Container _getDealItem() ...
You wil need to make sure that you also apply the same logic to _getDealRow and other nested calls.
I've created a flutter app where I'm managing array for todolist in app. I've can add the text by add button.
I've created a widget to show in list.
My question is how am i supposed manage the UI of individual.
Code:
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
_TodoListState createState() => new _TodoListState();
}
class _TodoListState extends State<TodoList> {
List _list = new List();
Widget listTile({String data: '[Empty data]'}) {
bool _writable = false;
TextEditingController _textController = new TextEditingController(text: data);
String _text = _textController.text;
if(!_writable){
return new Row(
children: <Widget>[
new Expanded(
child: new Text(data)
),
new IconButton(icon: new Icon(Icons.edit),
onPressed: () {
// setState(() {
_writable = ! _writable;
print(_writable.toString());
// });
}),
new IconButton(icon: new Icon(Icons.remove_circle), onPressed: null),
],
);
} else {
return new Row(
children: <Widget>[
new Expanded(
child: new TextField( controller: _textController )
),
new IconButton(icon: new Icon(Icons.done), onPressed: null),
],
);
}
}
void addInList(String string) {
print(string);
setState(() {
_list.add(string);
});
print(_list);
}
void removeFromList(int index){
}
static final TextEditingController _textController = new TextEditingController();
String get _text => _textController.text;
#override
Widget build(BuildContext context) {
Widget adderTile = new Row(
children: <Widget>[
new Expanded(
child:
new TextField(
textAlign: TextAlign.center,
controller: _textController ,
decoration: new InputDecoration( hintText: 'New item.!' ),
),
),
new IconButton(icon: new Icon(Icons.add), onPressed: (){addInList(_text);}),
],
);
return new MaterialApp(
title: 'TodoList',
home: new Scaffold(
appBar: new AppBar(title: new Text('TodoList'),),
body: new Column(
children: <Widget>[
adderTile,
new ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, int index){
return listTile(data: _list[index]);
}
),
],
),
),
);
}
}
if i change _writable inside setState then it rerenders widget and _writable becomes false again. if i do it without setState, then _writable becomes true but widget doesn't rerender.
P.S.: i don't want to add another array in to manage which is writable and which is not. Thanks in advance.
The variable
bool _writable = false;
is declared as local variable in the method listTile(), but should be moved next to List _list = new List(); to become a member variable. Then use setState() to set it and rebuild the view.
Edit:
You should create a dedicated StatefulWidget (TodoListEntry), having _writable as member as suggested above. Move almost the whole method body of listTile(...) to the build()-method of the TodoListEntryState, make the parameter String data also a member and pass the value via the constructor.
I know this code only displays title and i want to make a onTap method to navigate to a new route, but this is how fare i made it, any help, hint, tip, even shaming me for how stupid i am would be very much appreciated.
Edit: I did posted the whole code because something is going wrong even after help that i got here. maybe is a syntax problem or maybe i am just too stupid
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView.builder(
itemCount: data == null ? 0 : 10,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new ListTile(
onTap: _onTapped,
title : new Text(data[index]["title"]),
),
);
},
),
);
}
}
Just wrap your title in a GestureDecector to handle clicks.
Then call Navigator's pushNamed to redirect to a new route.
new GestureDetector(
onTap: () {
Navigator.pushNamed(context, "myRoute");
},
child: new Text("my Title"),
);
An easier approach I found is to just wrap the item inside the ListTile with a FlatButton (or some interactive widget). In your code, for example:
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView.builder(
itemCount: data == null ? 0 : 10,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new ListTile(
title: FlatButton(
child: new Text(data[index]["title"]),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourPage()),
);
},
),
),
);
},
),
);