I'm trying to create a word game. I have a local json file where I'm retrieving data from. I'm able to retrieve the data and display it on the first row. What I'm trying to do is on tap of one block (on the first row), get the value and display it in order on the second row.
I'm able to retrieve the value but I can't display it on the second row. I tested this by printing the value in the console.
Updated code:
body: new Container(
child: new Center(
// Use future builder and DefaultAssetBundle to load the local JSON file
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('data_repo/starwars_data.json'),
builder: (context, snapshot) {
var newData = JSON.decode(snapshot.data.toString());
List<Widget> listMyWidgets() {
List<Widget> list = new List();
for (var i = 0; i < newData.length; i++) {
var word = newData[i]['word']["letters"];
for (var n = 0; n < word.length; n++) {
list.add(new Text(word[n]['letter']));
}
}
return list;
}
List letters = [];
for (int i = 0; i < listMyWidgets().length; i++) {
var listMyWidgetsToString =
listMyWidgets()[i].toString();
var listWidgetToList =
listMyWidgetsToString.replaceAll('Text("', "");
var completeWordList =
listWidgetToList.replaceAll('")', "");
letters.add(completeWordList);
}
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
new Column(children: [
new Image.asset(newData[0]['image'])
]),
new GridView.count(
shrinkWrap: true,
crossAxisCount: listMyWidgets().length,
children: new List.generate(
listMyWidgets().length,
(i) => new GestureDetector(
onTap: () {
final int wordLength =
5; //this is a ref to the lenght of the word so you do not keep adding tiles
setState(() {
(letters.length + 1) <=
wordLength * 2
? letters.add(letters[i])
: null;
});
},
child: new Card(
elevation: 5.0,
color: Colors.brown[500],
child: new Padding(
padding:
const EdgeInsets.all(8.0),
child: new Center(
child:
new Text(letters[i])),
),
),
)),
),
],
),
);
},
itemCount: newData == null ? 0 : newData.length,
);
}),
),
)
It depends on how you want to structure your data. In this example, I just add the pressed letters into the same array for the word and it will do the job.
Note that I keep a reference (which you may add to your JSON) which is the initial length of the word so it stops adding tiles when all letters are used.
Also you need to have a StatefulWidget in order for this to work
Probably there is a better a way to handle this but that is what I managed to do atm.
class GridViewWords extends StatefulWidget {
#override
GridViewWordsState createState() {
return new GridViewWordsState();
}
}
class GridViewWordsState extends State<GridViewWords> {
List letters = [
"A",
"M",
"C",
"I",
"C"
];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new GridView.count(
shrinkWrap: true,
crossAxisCount: 5,
children: new List.generate(letters.length, (i)=>
new GestureDetector(
onTap: (){
final int wordLength =5; //this is a ref to the lenght of the word so you do not keep adding tiles
setState((){
(letters.length+1)<=wordLength*2? letters.add(letters[i]):null;
});
},
child: new Card(
elevation: 5.0,
color: Colors.brown[500],
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(child: new Text(letters[i])),
),
),
)),
),
);
}
}
Related
I am new to flutter. in my project, there is a various check_list_tile depending upon the length of the List (attendance list). And I have used one Boolean variable. Now when I press on one checkbox it automatically checks all other checkboxes. Please help me in this (on tap one checkbox should not change the state of all other checkboxes except clicked). I have copied all code please check check_box_list field.
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'dart:async';
Map map_student_data;
Iterable iter_student_data,iter_student_key;
List list_student_data,list_student_key;
bool t=true,checkbox=false;
List list;
String validation="yes";
int i;
int year;
final FirebaseDatabase database = FirebaseDatabase.instance;
class IImca_attendence extends StatefulWidget {
#override
_IImca_attendenceState createState() => _IImca_attendenceState();
}
class _IImca_attendenceState extends State<IImca_attendence> {
#override
void initState(){
this.check_year();
super.initState();
}
DateTime date = DateTime.now();
Future check_year()async{
var k= await database.reference().child("NITTE/CLASS/MCA").once().then((DataSnapshot snapshot){
Map sea= snapshot.value;
Iterable iter=sea.keys;
list=iter.toList();
list.sublist(list.length-1);
list.sort();
setState(() {
year=list.length-2;
});
check();
});
}
Future check()async{
var m=await database.reference().child("NITTE/CLASS/MCA/${list[year].toString().toUpperCase()}/STUDENT").once().then((DataSnapshot currentyear){
map_student_data=currentyear.value;
iter_student_data=map_student_data.values;
iter_student_key=map_student_data.keys;
list_student_data=iter_student_data.toList();
list_student_key=iter_student_key.toList();
for(i=0;i<=list_student_data.length;i++){
bool ss=true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("II-MCA"),
centerTitle: true,
actions: <Widget>[
IconButton(icon: Icon(Icons.refresh),onPressed: (){
setState(() {
check_year();
});
})
],
),
body:
validation=="1"?
new Center(
child: Text("STUDENT DOSE NOT EXIST IN $year",style: TextStyle(color: Colors.grey,fontWeight: FontWeight.bold,fontSize: 20),),
):
new ListView.builder(
itemCount: list_student_data==null?0
:list_student_data.length,
itemBuilder: (BuildContext context,int index){
var student_detail= ['NAME : ${list_student_data[index]['NAME']}','GENDER : ${list_student_data[index]['CURRENT CLASS']}','PHOTO : ${list_student_data[index]['PHOTO']}'];
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text("${list_student_key[index]}",style:TextStyle(fontWeight: FontWeight.bold,),),
subtitle: Text("NAME : ${list_student_data[index]['NAME']}"),
value: checkbox,
onChanged: (val){
setState(() {
checkbox=val;
if(checkbox==true){
print("${list_student_data[index]['NAME']}: i am absent");
}if(checkbox==false){
print("${list_student_data[index]['NAME']}: i am present");
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
)
);
}
}
Well... you are using a global checkbox variable, so It's quite normal that if you change it, all widget depending on its state will change accordingly.
What I suggest you to do is to add the selected state inside your model class. Just as an example, assumed you have this Student class (I know you are using firebase, but for sake of time I don't)
class Student {
var name = 'foo';
var year = '2018';
var selected = false;
Student(this.name);
}
This class has is selected state inside of it.
Now assume that your snapshot give you 3 students. Always for sake of time I've embedded a local array:
class _IImca_attendenceState extends State<IImca_attendence> {
var _students = [Student('foo'), Student('pub'), Student('beer')];
...
(Ellipses are not part of code... ;-])
I suggest you to put your state variables inside the Stateful Widget scope and not onto the Global Scope.
That said you could have:
ListView.builder(
itemCount: _students.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: _students[index].selected,
onChanged: (val) {
setState(() {
_students[index].selected = val;
if (!_students[index].selected) {
print(
'${_students[index].name}: i am absent');
}
if (_students[index].selected) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
)
You should also use an array of bool(s) of the same length of your snapshot data students array... but I'd like to suggest to track this information directly on your Student model.
UPDATE
As you are more comfortable using array I've change my code using a complementary array of bool of the same size of your student list.
All you have to do is an array (not a single value) of boolean values, the same size of your student array, lets call this list_student_present (I instead use list_student_present2)
At the beginning you initialize this in your check function a way like that:
list_student_data = iter_student_data.toList();
// This is the array you wanna use (first all false)
list_student_present = iter_student_data.map((_) => false).toList();
And then you will use this array of bool to check the state of your checkboxes:
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: list_student_present[index],
onChanged: (val) {
setState(() {
list_student_present[index] = val;
if (!list_student_present[index]) {
print(
'${_students[index].name}: i am absent');
}
if (list_student_present[index]) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
Do not use my Student class (I continue using it so that I'm able to show you data without firebase), continue using your students array list from your firebase snapshot.
Full code:
import 'package:flutter/material.dart';
import 'dart:async';
Map map_student_data;
Iterable iter_student_data, iter_student_key;
List list_student_data, list_student_key, list_student_present, list_student_present2;
bool t = true;
List list;
String validation = "yes";
int i;
int year;
class Student {
var name = 'foo';
var year = '2018';
var selected = false;
Student(this.name);
}
class IImca_attendence extends StatefulWidget {
#override
_IImca_attendenceState createState() => _IImca_attendenceState();
}
class _IImca_attendenceState extends State<IImca_attendence> {
var _students = [Student('foo'), Student('pub'), Student('beer')];
#override
void initState() {
this.check_year();
super.initState();
}
DateTime date = DateTime.now();
Future check_year() async {
Map sea = {1: 'atlantic', 2: 'pacific'};
Iterable iter = sea.keys;
list = iter.toList();
list.sublist(list.length - 1);
list.sort();
setState(() {
year = list.length - 2;
});
check();
}
Future check() async {
map_student_data = {
0: {'NAME': 'foo', 'CURRENT CLASS': 'pub', 'PHOTO': ''}
};
iter_student_data = map_student_data.values;
iter_student_key = map_student_data.keys;
list_student_data = iter_student_data.toList();
// This is the array you wanna use
list_student_present = iter_student_data.map((_) => false).toList();
// This is the array for my example
list_student_present2 = _students.map((_) => false).toList();
list_student_key = iter_student_key.toList();
for (i = 0; i <= list_student_data.length; i++) {
bool ss = true;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("II-MCA"),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
check_year();
});
})
],
),
body: validation == "1"
? new Center(
child: Text(
"STUDENT DOSE NOT EXIST IN $year",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 20),
),
)
: ListView.builder(
itemCount: _students.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: list_student_present2[index],
onChanged: (val) {
setState(() {
list_student_present2[index] = val;
if (!list_student_present2[index]) {
print(
'${_students[index].name}: i am absent');
}
if (list_student_present2[index]) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}));
}
}
I really don't like this solution. What I suggest you is to create your PODOs (Plain Old Dart Object) representing your firebase models and deserialise them from your firebase snapshots.
I have a parameter n, and i have to create n textfields and listen to them, and capture the value of all these fields.
Say I have to perform calculations on them. How do I achieve this? I tried to combine loops with widgets but I get lots of errors.
When I used a separate function to return a list of widgets for column's children property, it throws an error stating type int is not a subtype of type string of source.
generate a list from your d parameter and then generate a list of text field and text editing contotlers from that list
createTexttextfields (int d){
var textEditingControllers = <TextEditingController>[];
var textFields = <TextField>[];
var list = new List<int>.generate(d, (i) =>i + 1 );
print(list);
list.forEach((i) {
var textEditingController = new TextEditingController(text: "test $i");
textEditingControllers.add(textEditingController);
return textFields.add(new TextField(controller: textEditingController));
});
return textFields;
}
and then use this function in the children property of your widget for example the column widget
return new Scaffold(
appBar: new AppBar(),
body: new Column(
children: createTexttextfields(6),
),
);
But if you want to access them you can't do that by a function you must create them as variables
Widget build(BuildContext context) {
var d=5;//the number of text fields
var textEditingControllers = <TextEditingController>[];
var textFields = <TextField>[];
var list = new List<int>.generate(d, (i) =>i + 1 );
list.forEach((i) {
var textEditingController = new TextEditingController(text: "test $i");
textEditingControllers.add(textEditingController);
return textFields.add(new TextField(controller: textEditingController));
});
return new Scaffold(
appBar: new AppBar(),
body: new Column(
children: textFields),
floatingActionButton: new FloatingActionButton(
onPressed: (){
//clear the text in the second TextEditingController
textEditingControllers[1].clear();
} ,
),
);
}
}
Full Example
return Container(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 60,
itemBuilder: (context, index){
return ButtonTheme(
minWidth: 20.0,
height: 20.0,
child: MaterialButton(
color:AppTheme.colorDark,
colorBrightness: Brightness.dark,
onPressed: () => print(index),
shape: RoundedRectangleBorder(borderRadius:
BorderRadius.circular(10.0)),
child: Text('$index'),
),
);
},
),
);
Very generic example of creating a List<Widget> from a List:
Widget _buildColumn() {
var stringList = ["a", "b", "c"];
// Create a List<Text> (or List<MyWidget>) using each String from stringList
var textList = stringList.map<Text>((s) => Text(s)).toList();
// use that list however you want!
return Column(children: textList);
}
Looks like this :
I have a local json file that I'm able to retrieve all the information I need. However, I can't seem to display it like I prefer. I have a list called list and would like to display each element (each letter) as a column so I can display it with a padding and change the font. I'm trying to create a game similar to word connect.
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
List data;
#override
Widget build(BuildContext context) {
final title = 'Basic List';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text("Load local JSON file"),
),
body: new Container(
child: new Center(
// Use future builder and DefaultAssetBundle to load the local JSON file
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('data_repo/starwars_data.json'),
builder: (context, snapshot) {
var newData = JSON.decode(snapshot.data.toString());
List<Widget> listMyWidgets(){
List<Widget> list = new List();
for( var i = 0; i < newData.length; i++){
var word = newData[i]['word']["letters"];
for( var n = 0; n < word.length; n++){
list.add(new Text(word[n]['letter']));
}
}
return list;
}
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children:[
new Column(
children:[
new Image.asset( newData[index]['image'])
]
),
new Row(
children:listMyWidgets()
)
],
),
);
},
itemCount: newData == null ? 0 : newData.length,
);
}),
),
)
));
}
}
Something similar to this:
Ah, I think what you are asking about is GridView.
class GridViewWords extends StatelessWidget {
List letters = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k"
];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
children: new List.generate(letters.length, (i)=>
new Card(
elevation: 5.0,
color: Colors.brown[500],
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(child: new Text(letters[i])),
),
)),
),
);
}
}
UPDATE
This is the StreamBuilder code:
I am trying currently to update with a timer that runs a Stream.fromFuture which updates the data, but with the flicker and scroll weirdness.
new StreamBuilder(
initialData: myInitialData,
stream: msgstream,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
myInitialData = snapshot.data;
return new RefreshIndicator(
child: new ListView.builder(
itemBuilder: (context, index) {
Stream<List<Map>> msgstream2;
Future<List<Map>> _responseDate = ChatDB.instance.getMessagesByDate(snapshot.data[index]['msgkey'], snapshot.data[index]['msgdate']);
msgstream2 = new Stream.fromFuture(_responseDate);
return new StreamBuilder(
initialData: myInitialData2,
stream: msgstream2,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
List messList;
var mybytes;
File myimageview;
Image newimageview;
String imgStr;
String vidStr;
String vidimgstr;
myInitialData2 = snapshot.data;
List<dynamic> json = snapshot.data;
List messagelist = [];
json.forEach((element) {
DateTime submitdate =
DateTime.parse(element['submitdate']).toLocal();
String myvideo = (element['chatvideo']);
String myimage = element['chatimage'];
String myvideoimage = element['chatvideoimage'];
File imgfile;
File vidfile;
File vidimgfile;
bool vidInit = false;
Future<Null> _launched;
String localAssetPath;
String localVideoPath;
String mymessage = element['message'].replaceAll("[\u2018\u2019]", "'");
//print('MYDATE: '+submitdate.toString());
_checkFile(File file) async {
var checkfile = await file.exists();
print('VIDEXISTS: '+checkfile.toString());
}
Future<Null> _launchVideo(String url, bool isLocal) async {
if (await canLaunchVideo(url, isLocal)) {
await launchVideo(url, isLocal);
} else {
throw 'Could not launch $url';
}
}
void _launchLocal() =>
setState(() => _launched = _launchVideo(localVideoPath, true)
);
Widget _showVideo() {
return new Flexible(
child: new Card(
child: new Column(
children: <Widget>[
new ListTile(subtitle: new Text('Video'), title: new Text(element['referralname']),),
new GestureDetector(
onTap: _launchLocal,
child: new Image.file(
vidimgfile,
width: 150.0,
),
),
],
),
)
);
}
if (myimage != "") {
imgStr = element['chatimage'];
imgfile = new File(imgStr);
}
if (myvideo != "") {
vidStr = element['chatvideo'];
vidimgstr = element['chatvideoimage'];
vidimgfile = new File(vidimgstr);
localVideoPath = '$vidStr';
}
_showLgPic() {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/ShowPic"),
builder: (BuildContext context) => new ShowPic(
image: imgfile,
),
);
Navigator.of(context).push(route);
}
Widget _showGraphic() {
Widget mywidget;
if (myimage != "") {
mywidget = new GestureDetector(
child: new Image.file(
imgfile,
width: 300.0,
),
onTap: _showLgPic,
);
} else if (myvideo != "") {
mywidget = _showVideo();
} else {
mywidget = new Container();
}
return mywidget;
}
messagelist.add(
new Container(
//width: 300.0,
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
padding: new EdgeInsets.only(bottom: 5.0),
child: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new CircleAvatar(
child: new Text(
element['sendname'][0],
style: new TextStyle(fontSize: 15.0),
),
radius: 12.0,
),
new Text(' '),
new Text(
element['sendname'],
style: new TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold),
),
new Text(' '),
new Text(
new DateFormat.Hm().format(submitdate),
style: new TextStyle(
color: Colors.grey, fontSize: 12.0),
),
],
),
),
new Row(
children: <Widget>[
new Text(' '),
new Flexible(
child: new Text(mymessage),
)
],
),
new Container(
width: 150.0,
child: new Row(
children: <Widget>[
new Text(' '),
_showGraphic()
],
)),
],
),
),
);
});
return new Column(children: messagelist);
}
}
);
/*return new MyChatWidget(
datediv: snapshot.data[index]['msgdate'],
msgkey: snapshot.data[index]['msgkey'],
);*/
},
//itemBuilder: _itemBuilder,
controller: _scrollController,
reverse: true,
itemCount: snapshot.data.length,
),
onRefresh: _onRefresh
);
}
}
}),
I started with a Future> from a local sqlite DB. I take that future and use the data to to get another Future> from the DB. I use Listview.builder to build the widget, etc... All works great, but need to refresh the data in realtime as messages come in and get updated in the DB. I converted the furtures to streams and use a timer to go get new data, but of course my screen flickers and eventhough the data refreshes its ugly.
So I am trying to figure out a better way to get the data to update without the flicker and not affect the user if they are scrolling on the page looking at messages.
I currently do the 2 futures, because one is used to build Date Dividers between messages for each Date. I have been looking at have a StreamController and subscribing to it, but not clear how to update the data in the controller as the the data needs to be full when they come to the page and then added to as the new messages sync.
So I have been looking at something like this that I found:
class Server {
StreamController<List<Map>> _controller = new StreamController.broadcast();
void addMessage(int message) {
var newmsg = await database.rawQuery('select c.*, date(submitdate, "localtime") as msgtime from v_groupchats g join chats c on c.id = g.id where (oid = $oid or prid = $prid) and c.msgkey not in (select msgkey from chatArchive) order by submitdate desc');
_controller.add(message);
}
Stream get messages => _controller.stream;
}
This is not complete, just hoping it helps someone with some ideas for me.
Thanks in advance for any help.
This flickering is most likely the result of your:
case ConnectionState.waiting:
return new Text('');
Because every time you fetch data, your stream will enter a brief moment of ConnectionState.waiting before it is ConnectionState.done. And what you did was tell the UI to display essentially nothing Text('') every time while it fetches data. Even if it only takes like 50ms, it's noticeable to the human eye...
So if you don't care while your stream is fetching data, then remove that check. Otherwise, you could change your layout into a Stack and overlay a loading animation somewhere, or just something to indicate that it's currently fetching data.
(note that I was able to see this flickering when I tried to test your setup with a simulated Future.delayed(const Duration(milliseconds: 50), () => data), and we can perceive quicker still)
I have a FutureBuilder that gets DISTINCT dates from a local sqlite DB, then I take each date and get the messages for those dates to put them in the widget, this works fine, until you want to listen realtime to a stream or poll for new messages which rebuilds the widgets and flickers the page and then scrolls to the beginning each time. I am hoping to find a way to take all the data into some object or other widget and then group by date and order, etc.. This way I can listen to a stream for updated messages, etc..
Any help would be great, here is my code if it helps anyone see what I do, this is after I converted to Streambuilder, but same result.
new StreamBuilder(
initialData: myInitialData,
stream: msgstream,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
myInitialData = snapshot.data;
return new RefreshIndicator(
child: new ListView.builder(
itemBuilder: (context, index) {
return new MyChatWidget(
datediv: snapshot.data[index]['msgdate'],
msgkey: snapshot.data[index]['msgkey'],
);
},
//itemBuilder: _itemBuilder,
controller: _scrollController,
reverse: true,
itemCount: snapshot.data.length,
),
onRefresh: _onRefresh
);
}
}
}),
This is the Widget that the StreamBuilder calls:
class MyChatWidget extends StatefulWidget {
MyChatWidget({Key key, this.datediv, this.msgkey}) : super(key: key);
final String datediv;
final String msgkey;
#override
_MyChatWidgetState createState() => new _MyChatWidgetState();
}
class _MyChatWidgetState extends State<MyChatWidget> {
List<Widget> messagelist;
int messagecount = 0;
var jsonCodec = const JsonCodec();
var mydate = '';
var _urlMessages = '';
PageStorageKey _key;
VideoPlayerController vcontroller;
//Future<http.Response> _responseFuture;
Future<List<Map>> _responseFuture;
List messList;
var mybytes;
File myimageview;
Image newimageview;
String imgStr;
String vidStr;
String vidimgstr;
bool submitting = false;
List<Map> myInitialData;
Stream<List<Map>> msgstream;
#override
void initState() {
super.initState();
if (new DateFormat.yMd().format(DateTime.parse(widget.datediv)) ==
new DateFormat.yMd().format(new DateTime.now())) {
mydate = 'Today';
} else {
mydate = new DateFormat.yMMMEd().format(DateTime.parse(widget.datediv));
}
DateChatMessage dcm =
new DateChatMessage(widget.msgkey, widget.datediv.toString());
var json = jsonCodec.encode(dcm);
_urlMessages =
'http://loop-dev.clinicalsoftworks.com/chat/messages/getbydate';
//_responseFuture = http.post(_urlMessages, body: json, headers: getAuthHeader());
_responseFuture =
ChatDB.instance.getMessagesByDate(widget.msgkey, widget.datediv);
msgstream = new Stream.fromFuture(_responseFuture);
//controller = new TabController(length: 4, vsync: this);
//_getMessages();
}
/*#override
void dispose() {
super.dispose();
if (vcontroller != null) {
vcontroller.dispose();
}
}*/
#override
Widget build(BuildContext context) {
_key = new PageStorageKey('${widget.datediv.toString()}');
return new Column(
children: <Widget>[
new Container(
child: new Text(
mydate,
textAlign: TextAlign.left,
style: new TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
alignment: Alignment.centerLeft,
padding: new EdgeInsets.only(left: 10.0),
),
new Container(
child: new Divider(
height: 5.0,
color: Colors.grey,
),
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
),
/**/
new StreamBuilder(
initialData: myInitialData,
stream: msgstream,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
myInitialData = snapshot.data;
List<dynamic> json = snapshot.data;
messagelist = [];
json.forEach((element) {
DateTime submitdate =
DateTime.parse(element['submitdate']).toLocal();
String myvideo = (element['chatvideo']);
String myimage = element['chatimage'];
String myvideoimage = element['chatvideoimage'];
File imgfile;
File vidfile;
File vidimgfile;
bool vidInit = false;
Future<Null> _launched;
String localAssetPath;
String localVideoPath;
String mymessage = element['message'].replaceAll("[\u2018\u2019]", "'");
//print('MYDATE: '+submitdate.toString());
_checkFile(File file) async {
var checkfile = await file.exists();
print('VIDEXISTS: '+checkfile.toString());
}
Future<Null> _launchVideo(String url, bool isLocal) async {
if (await canLaunchVideo(url, isLocal)) {
await launchVideo(url, isLocal);
} else {
throw 'Could not launch $url';
}
}
void _launchLocal() =>
setState(() => _launched = _launchVideo(localVideoPath, true)
);
Widget _showVideo() {
/*return new Flexible(
child: new vplayer.VideoCard(
controller: vcontroller,
title: element['referralname'],
subtitle: 'video',
),
);*/
return new Flexible(
child: new Card(
child: new Column(
children: <Widget>[
new ListTile(subtitle: new Text('Video'), title: new Text(element['referralname']),),
new GestureDetector(
onTap: _launchLocal,
child: new Image.file(
vidimgfile,
width: 150.0,
),
),
],
),
)
);
}
_initVideo() {
setState(() {vidInit = true;});
}
_onError() {
print('VIDEO INIT ERROR');
}
if (myimage != "") {
imgStr = element['chatimage'];
imgfile = new File(imgStr);
}
if (myvideo != "") {
vidStr = element['chatvideo'];
vidimgstr = element['chatvideoimage'];
vidimgfile = new File(vidimgstr);
//vidfile = new File(vidStr);
//_checkFile(vidfile);
//print('vidfile: '+vidfile.path);
localVideoPath = '$vidStr';
//print('LOCALVIDEO: '+localVideoPath);
//vcontroller = new VideoPlayerController('file://$vidStr')..initialize();
}
_showLgPic() {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/ShowPic"),
builder: (BuildContext context) => new ShowPic(
image: imgfile,
),
);
Navigator.of(context).push(route);
}
Widget _showGraphic() {
Widget mywidget;
if (myimage != "") {
mywidget = new GestureDetector(
child: new Image.file(
imgfile,
width: 300.0,
),
onTap: _showLgPic,
);
} else if (myvideo != "") {
mywidget = _showVideo();
} else {
mywidget = new Container();
}
return mywidget;
}
messagelist.add(
new Container(
//width: 300.0,
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
padding: new EdgeInsets.only(bottom: 5.0),
child: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new CircleAvatar(
child: new Text(
element['sendname'][0],
style: new TextStyle(fontSize: 15.0),
),
radius: 12.0,
),
new Text(' '),
new Text(
element['sendname'],
style: new TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold),
),
new Text(' '),
new Text(
new DateFormat.Hm().format(submitdate),
style: new TextStyle(
color: Colors.grey, fontSize: 12.0),
),
],
),
),
new Row(
children: <Widget>[
new Text(' '),
new Flexible(
child: new Text(mymessage),
)
],
),
new Container(
width: 150.0,
child: new Row(
children: <Widget>[
new Text(' '),
_showGraphic()
/*myimage != ""
? new GestureDetector(
child: new Image.file(
imgfile,
width: 300.0,
),
onTap: _showLgPic,
)
: myvideo != "" ? _showVideo() : new Container(),*/
],
)),
],
),
),
);
});
return new Column(children: messagelist);
}
},
)
],
);
}
}
Thanks for any assistance
which rebuilds the widgets and flickers the page and then scrolls to the beginning each time
To solve problem with scrolling try ScrollController. Create your own, keep it between updates and inject into List you created.
To solve flickering you could use Key for List widgets. Key should be unique identifier of message, e.g. msgkey
This example how to keep scrolloffset works for me
class SomeWidget extends StatefulWidget {
#override
_SomeWidgetState createState() => new _SomeWidgetState();
}
class _SomeWidgetState extends State<SomeWidget> {
ScrollController _scrollController;
int _count;
#override
void initState() {
super.initState();
_count = 10;
_scrollController = new ScrollController();
}
void _add() {
setState(() => _count += 5);
}
#override
Widget build(BuildContext context) {
final _titles = new List<String>.generate(_count, (i) => 'Title ${i}');
return new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.add), onPressed: _add)
],
),
body: new ListView.builder(
controller: _scrollController,
itemCount: _titles.length,
itemBuilder: (context, index) => new ListTile(
title: new Text(_titles[index]),
),
),
);
}
}