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]),
),
),
);
}
}
Related
I am saving a list of favorites in my game app, I can add or remove games from the list, but on the removeFavorite method, when I use setState in "games.remove(index)" the listview.separeted doesn't update. If close and open favorite screen, the list is updated but while I am at the favorite screen it doesn't update.
class _FavoriteScreenState extends State<FavoriteScreen> {
List<dynamic> favoriteList = [];
List<Game> games = [];
#override
void initState() {
loadFavorites();
super.initState();
}
#override
Widget build(BuildContext context) {
Widget _buildListView(Game game, int index){
return InkWell(
child: Container(
height: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(left: 5),
child: Image.network(game.cover),
),
),
Expanded(
flex: 2,
child: Container(
child: Row(
children: <Widget>[
SizedBox(width: 15,),
Text(
game.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w300,
fontSize: 14
),
),
],
),
),
)
],
),
),
onLongPress: (){
_showDialog(index);
},
);
}
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
appBar: AppBar(
title: Text("Favorites"),
),
body: Container(
margin: EdgeInsets.fromLTRB(16, 16, 16, screenHeight > 720 ? 90 : 62),
child: ListView.separated(
separatorBuilder: (BuildContext context, int index) => Divider(color: Colors.black,),
itemCount: games.length,
itemBuilder: (context, index){
return _buildListView(games[index], index);
},
)
),
);
}
Future<File> getFile() async{
final directory = await getApplicationDocumentsDirectory();
return File("${directory.path}/favorites.json");
}
Future<String> readFavorite() async{
try{
// Le e retorna o arquivo como String
final file = await getFile();
return file.readAsString();
} catch (e){
return null;
}
}
void loadFavorites() {
readFavorite().then((data){
// Transforma o arquivo JSON numa List
favoriteList = json.decode(data);
if(favoriteList.length > 0 && favoriteList != null){
favoriteList.forEach((map){
Game game = Game(map["cover"], map["title"], map["description"], map["url"]);
setState(() {
games.add(game);
});
});
} else {
}
print(games.length);
});
}
Future<File> saveFile() async{
String data = json.encode(favoriteList);
final file = await getFile();
return file.writeAsString(data);
}
void _showDialog(int index){
showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
content: Text("?",
style: TextStyle(fontSize: 18) ,),
actions: <Widget>[
FlatButton(
child: Text("YES"),
onPressed: (){
Navigator.of(context).pop();
removeFavorite(index);
}
),
FlatButton(
child: Text("NO"),
onPressed: (){
Navigator.of(context).pop();
}
),
],
);
}
);
}
void removeFavorite(int index){
favoriteList.forEach((m) {
Map<String, dynamic> map = m;
if (map.containsValue(games[index].title)) {
favoriteList.remove(m);
saveFile();
setState(() {
games.remove(index);
});
}
});
}
}
My bad, I replaced the remove with removeAt and it worked.
setState(() {
games.removeAt(index);
});
I have a stream builder that shows a list of "posts" from a server. I have used the BLoC architecture to accomplish this. But for some reason when I switch tabs and back the posts disappear how can I keep the posts from disappearing or have them re-render? Below is small part of my code I think is relevant I can add more if needed:
Tab UI (not all the code, file containing BLoC is imported at top):
#override
void initState() {
bloc.fetchMyPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Posts", style: Style.appBarStyle),
bottom: TabBar(
tabs: [
Tab(
text: "My Posts",
),
Tab(
text: "My Other Posts",
),
],
),
),
body: TabBarView(
children: [
Posts(stream: bloc.myPosts), //Stream builder with SliverChildBuilderDelegate
Posts(stream:bloc.myOtherPosts),//Stream builder with SliverChildBuilderDelegate
],
),
),
);
}
Stream Builder (Posts):
Widget Posts({Stream stream, //Other variables}) {
return StreamBuilder(
stream:stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch(snapshot.connectionState) {
case ConnectionState.none:
return Row(
children: <Widget>[
Flexible(
child: Text("Please check if you are connected to the internet"),
),
],
);
break;
case ConnectionState.waiting:
if (snapshot.data == null){
return Container(
color: Color(0xFFF4F4FF),
child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
} else return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
Center(
child: Text("Loading"),
),
],
);
break;
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.hasData) {
return Container(
color:Colors.white,
child: CustomScrollView(
scrollDirection: Axis.vertical,
shrinkWrap: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => PostCard(post:snapshot.data[index],//variables),
childCount: snapshot.data.length,
),
),
)
],
));
}
if (snapshot.data == null){
return Container(
color: Color(0xFFF4F4FF),
child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
}
}
});
}
BLoC:
class Bloc{
ApiClient _client = ApiClient();
final _myPosts = BehaviourSubject<List<Post>>();
final _myOtherPosts = BehaviourSubject<List<Post>>();
Stream<List<Post>> get myPosts => _myPosts.stream;
Stream<List<Post>> get myOtherPosts => _myOtherPosts.stream;
fetchMyPosts() async {
List<Post> posts = await _client.getMyPosts();
_myPosts.sink.add(posts);
}
fetchMyOtherPosts() async {
List<Post> posts = await _client.getMyOtherPosts();
_myOtherPosts.sink.add(posts);
}
dispose(){
_myPosts.close();
_myOtherPosts.close();
}
}
final bloc = Bloc();
Main Screen:
class MainScreen extends StatefulWidget {
UserBloc userBloc;
MainScreen({this.userBloc});
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
Widget getPage(int index) {
if (index == 0) {
return PostPage(myHandle: widget.userBloc.userValue);
}
if (index == 1) {
return PageOne();
}
if (index == 3) {
return PageTwo();
}
if (index == 4) {
return PageThree(userBloc: widget.userBloc);
}
return PostPage(userBloc: widget.userBloc);
}
Widget customNav() {
return Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.library_books),
onPressed: () => setState(() {
_currentIndex = 0;
})),
// MORE ICONS but similar code
],
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: <Widget>[
getPage(_currentIndex),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: customNav(),
),
]));
}
}
Take a look at this code i put some comments. I can do this using streambuilder and bloc pattern simulating an async data fetching with future delayed. This widget is working but you will need adapt to your needs.
class TabWidget extends StatefulWidget {
#override
_TabWidgetState createState() => _TabWidgetState();
}
class _TabWidgetState extends State<TabWidget> with SingleTickerProviderStateMixin {
Bloc _bloc;
#override
void initState() {
super.initState();
_bloc = Bloc(); // can be your bloc.fetchData();
}
#override
void dispose() {
_bloc?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//i really recomment using stream builder to create all layout
// if length property is dynamic
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Tab screen"),
bottom: TabBar(
tabs: [
Tab( text: "My Posts" ),
Tab( text: "Other" ),
],
),
),
body: StreamBuilder<List<Widget>>(
stream: _bloc.getTabData,
builder: (context, asyncSnapshot){
switch(asyncSnapshot.connectionState){
case ConnectionState.none:
return Row(
children: <Widget>[
Flexible(
child: Text("handle none state here, this is because i am simulate a async event"),
),
],
);
break;
case ConnectionState.waiting:
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
Center(
child: Text("Loading data..."),
),
],
);
break;
case ConnectionState.active:
case ConnectionState.done:
//assuming that snapshot has valid data...
return TabBarView(
children:[
asyncSnapshot.data[0],
asyncSnapshot.data[1],
],
);
}
}
),
),
);
}
}
class Bloc{
// post items
// just to simulate data
List<Widget> _tabList1 = List.generate(10, (index){ return Text("TAB 1 Item $index");} );
List<Widget> _tabList2 = List.generate(10, (index){ return Text("TAB 2 Item $index");} );
//tab's data stream
PublishSubject< List<Widget>> _tabData = PublishSubject();
Observable<List<Widget>> get getTabData => _tabData.stream;
Bloc() {
Future.delayed(Duration(seconds: 5), () {
List<Widget> tabDataWidgets = List();
// adding tab's data
tabDataWidgets.add( ListView(
children: _tabList1,
) );
tabDataWidgets.add( ListView(
children: _tabList2,
) );
_addingToSink( tabDataWidgets );
});
}
void _addingToSink( final List<Widget> list) => _tabData.sink.add( list );
dispose(){ _tabData?.close(); }
}
I changed PublishSubject to BehaviourSubject and it seemed to work. I used it in conjunction with Marcos Boaventura's answer as well. Although I used two stream builders.
PublishSubject: Starts empty and only emits new elements.
BehaviorSubject: It needs an initial value and replays it or the latest element to new subscribers.
I have fixed it another way like as I have take two Streem variable Example
final Stream<QuerySnapshot> showpost = FirebaseFirestore.instance
.collection("post")
.snapshots();
final Stream<QuerySnapshot> showpost2 = FirebaseFirestore.instance
.collection("post")
.snapshots();
And two-stream i have use two-variable ;
File:homepage.dart
class _HomePageState extends State<HomePage> {
var _scaffoldBody;
var _scaffoldTitle;
#override
initState() {
_scaffoldTitle=new Text("Wall");
_scaffoldBody=new Center(child:CircularProgressIndicator());
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: new Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color:Theme.of(context).accentColor,
),
),
ListTile(
title: Text('Home'),
onTap: () {
setState(() {
_scaffoldTitle=new Text("Home");
_scaffoldBody=new Text("Home Page");
});
Navigator.pop(context);
},
),
ListTile(
title: Text('MenuItem1'),
onTap: () {
setState(() {
_scaffoldTitle=new Text("1st Menu");
_scaffoldBody=new TestPage("Page 1");
});
Navigator.pop(context);
},
),
ListTile(
title: Text('MenuItem2'),
onTap: () {
setState(() {
_scaffoldTitle=new Text("2nd Item");
_scaffoldBody=new TestPage("Page 2");
});
Navigator.pop(context);
},
),
],
),
),
appBar: new AppBar(
title: _scaffoldTitle,
elevation: 2.0,
actions: <Widget>[
],
),
body:_scaffoldBody,
);
}
file: TestPage.dart
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget{
final String rollNumber;
TestPage(this.rollNumber);
#override
TestPageState createState() => new TestPageState(rollNumber);
}
class TestPageState extends State<TestPage>{
String rollNumber;
TestPageState(this.rollNumber);
#override
Widget build(BuildContext context) {
return new Text(rollNumber);
}
}
Output:
When I chose : "Home" from drawer it shows "Home Page"
Then I chose : "MenuItem1" it showed "Page 1"
Then I chose : "MenuItem2" it showed same "Page 1" (unexpected)
Then I chose : "Home" it showed "Home Page"
Then I chose : "MenuItem2" it showed correctly as "Page 2"
Then I chose : "MenuItem1" it showed "Page 2" (unexpected)
unable to solve this.Stuck!! Any solutions are appreciated. Thanks in advance!!
Update your TestPart.dart as follow:
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget{
final String rollNumber;
TestPage(this.rollNumber);
#override
TestPageState createState() => new TestPageState();
}
class TestPageState extends State<TestPage>{
#override
Widget build(BuildContext context) {
return new Text(widget.rollNumber);
}
}
My StudentPage Class
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'calendar_utils.dart';
import 'dart:async';
final mainReference = FirebaseDatabase.instance.reference();
class StudentPage extends StatefulWidget{
final String rollNumber;
StudentPage(this.rollNumber);
#override
StudentPageState createState() => new StudentPageState(rollNumber);
}
class StudentPageState extends State<StudentPage>{
final String currentRoll,currentYear="2018-19";
StudentPageState(this.currentRoll);
List<String> academicMonth=["June","July","August","September","October","November","December",
"January","February","March","April","May"];
int firstHalfYear=2018,secondHalfYear=2019;
List<Widget> _monthListArray=[new ListTile(title:new Text("Academic Year",style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0),),)];
List<Widget> _listView;
int _no_of_working=0;
int _no_of_present=0;
#override
void initState() {
// TODO: implement initState
_listView=[new Center(
child: new CircularProgressIndicator(),
)];
_loadMonths();
}
#override
Widget build(BuildContext context) {
return new RefreshIndicator(child: new ListView(
children: _listView,
), onRefresh: _loadMonths);
}
Future<Null> _loadMonths() async {
_listView.clear();
await mainReference.child("XXXX").child("attendance").child(
widget.rollNumber).child(currentYear).once().then((
DataSnapshot dataSnapshot) {
try {
int monthIndex=5; //monthIndex starts from June
for(var month in academicMonth){
debugPrint("Month:"+month);
monthIndex=(monthIndex+1)%12; //month index cycles throughout 1-12
if(monthIndex==0) monthIndex=12;
List<Widget> _daysList=[];
_monthListArray.add(new Padding(padding: EdgeInsets.all(16.0),child: new Text(month+" "+(monthIndex<6?secondHalfYear:firstHalfYear).toString(),style: new TextStyle(color: Colors.black87,fontWeight: FontWeight.bold,fontSize: 20.0),),)); //initializing the month
_monthListArray.add(new Padding(padding: EdgeInsets.only(top: 10.0,bottom: 10.0),child:
new Row(children: <Widget>[
new Expanded(
child: new Center(child: new Text("S",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("M",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("T",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("W",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("T",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("F",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
new Expanded(
child: new Center(child: new Text("S",style: new TextStyle(fontWeight: FontWeight.bold),),),
),
],),));
debugPrint(monthIndex.toString());
int freeSpace=CalendarUtils(1,monthIndex,monthIndex<6?secondHalfYear:firstHalfYear).getDayFromDate();
debugPrint("FreeSpace---"+freeSpace.toString());
if(freeSpace!=0){
for (var i = 0; i < freeSpace; i++) {
_daysList.add(new Text(""));
}
}
debugPrint(monthIndex.toString());
var year=monthIndex<6?secondHalfYear:firstHalfYear;
debugPrint("Year"+year.toString());
debugPrint("Forloop limit:"+CalendarUtils(1,monthIndex,monthIndex<6?firstHalfYear:secondHalfYear).numberOfDays().toString());
for(var day=1;day<=CalendarUtils(1,monthIndex,monthIndex<6?secondHalfYear:firstHalfYear).numberOfDays();day++){
try {
//debugPrint(day.toString()+":"+dataSnapshot.value[month][day].toString());
if (CalendarUtils(day, monthIndex,
monthIndex < 6 ? secondHalfYear: firstHalfYear)
.getDayFromDate() != 0){
if (dataSnapshot.value[month][day].toString() == "1") {
_no_of_working++;
_no_of_present++;
_daysList.add(
new Padding(padding: EdgeInsets.only(left: 10.0,right: 10.0,top: 2.0,bottom: 2.0),
child: new Container(
alignment: Alignment.center,
width: 30.0,
height: 30.0,
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
color: Colors.green),
child: new Text(
day.toString(),
style: new TextStyle(color: Colors.white),
),
),
)
);
}
else if (dataSnapshot.value[month][day].toString() == "0"){
_no_of_working++;
_daysList.add(
new Padding(padding: EdgeInsets.only(left: 10.0,right: 10.0,top: 2.0,bottom: 2.0),
child: new Container(
alignment: Alignment.center,
width: 30.0,
height: 30.0,
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
color: Colors.redAccent),
child: new Text(
day.toString(),
style: new TextStyle(color: Colors.white),
),
),
)
);
}
else {
_daysList.add(
new Padding(padding: EdgeInsets.only(left: 10.0,right: 10.0,top: 2.0,bottom: 2.0),
child: new Container(
alignment: Alignment.center,
width: 30.0,
height: 30.0,
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
),
child: new Text(
day.toString(),
style: new TextStyle(color: Colors.black),
),
),
)
);
}
}else {
_daysList.add(
new Center(child: new Text(day.toString(), style:
new TextStyle(color: Colors.black45),)));
}
}catch (e){
_daysList.add(new Center(child:new Text(day.toString(),style:
new TextStyle(color: Colors.black),) ));
}
}
Widget _daysGrid=new GridView.count(crossAxisCount: 7,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
childAspectRatio: 1.5,
children: _daysList,
);
_monthListArray.add(_daysGrid);
}
}catch(e){}
_monthListArray.add(new Text((_no_of_present/_no_of_working).toString()));
});
this.setState((){
_listView=_monthListArray;
});
}
}
My HomePage.dart
import 'package:flutter/material.dart';
import 'package:smart_school_parent/TestPage.dart';
import 'package:smart_school_parent/attendance.dart';
import 'package:smart_school_parent/post.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:smart_school_parent/auth.dart';
import 'dart:async';
class HomePage extends StatefulWidget {
final BaseAuth auth;
final VoidCallback onSignOut;
HomePage({Key key, this.auth, this.onSignOut}) : super(key: key);
#override
_HomePageState createState() => new _HomePageState(this.auth);
}
class _HomePageState extends State<HomePage> {
final mainReference = FirebaseDatabase.instance.reference();
List<PostData> post_list = new List();
var _scaffoldBody;
var _loading;
var _currentYear;
var _scaffoldTitle;
List<Widget> _childrenList=[new Text("Profiles",textAlign: TextAlign.left,style: new TextStyle(fontWeight: FontWeight.bold),)];
BaseAuth auth;
_HomePageState(this.auth);
#override
initState() {
//_children=updateChildren();
_updateChildren();
getList();
_loading=true ;
_scaffoldTitle=new Text("Wall");
_scaffoldBody=new Center(child:CircularProgressIndicator());
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: new Drawer(
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('XXXXx'),
decoration: BoxDecoration(
color:Theme.of(context).accentColor,
),
),
ListTile(
title: Text('Wall'),
onTap: () {
setState(() {
_scaffoldTitle=new Text("Wall");
_scaffoldBody=new RefreshIndicator(child: _loadWall(), onRefresh: getList);
});
Navigator.pop(context);
},
),
Column(
children: _childrenList,
),
],
),
),
appBar: new AppBar(
title: _scaffoldTitle,
elevation: 2.0,
actions: <Widget>[
],
),
body:_scaffoldBody,
);
}
Future <Null> getList() async {
await mainReference.child("NISE-Coimbatore").child("posts").once().then((DataSnapshot dataSnapshot) {
this.setState(() {
post_list.clear();
if(dataSnapshot.value!=null){
for (var value in dataSnapshot.value.values) {
post_list.add(new PostData.fromJson(value));
//debugPrint(value.toString());
}
}else{
this.setState((){
});
}
});
});
setState(() {
_scaffoldBody=new RefreshIndicator(child: _loadWall(), onRefresh: getList);
});
}
Widget _loadWall(){
return Stack(
children: <Widget>[
new ListView.builder(itemBuilder: (BuildContext context,int index){
return new Post(post_list[index].image,post_list[index].title,post_list[index].content);
},
itemCount: post_list == null ? 0 : post_list.length,)
],
);
}
_updateChildren() async {
_currentYear= await mainReference.child("attendance").child("currentYear").once();
await auth.currentUser().then((String userId) async{
mainReference.child("NISE-Coimbatore").child("parents").child(userId).child('children').once().then((DataSnapshot dataSnapshot){
for (var value in dataSnapshot.value){
this.setState((){
_childrenList.add(
new Padding(
padding: EdgeInsets.only(left: 25.0),
child: ListTile(
title: Text(value.toString()),
onTap: () {
super.setState(() {
_scaffoldTitle=new Text(value.toString());
this._scaffoldBody=new StudentPage(value.toString());
});
Navigator.pop(context);
},
),
),
);
});
//debugPrint("Childrrncount::"+value.toString());
}
});
});
}
}
class PostData{
String title;
String content;
String image;
PostData(this.title, this.content, this.image);
PostData.fromJson(var value) {
this.title = value['title'];
this.content = value['content'];
this.image = value['image'];
}
}
_childrenList has two elements. The datasnapshot.values has [15505,15501] two roll numbers
Both rollnumbers from firebase appear on the drawer box and on tap it is expected to show StudentPage(15501.tostring()) or StudentPage(15505.tostring())
it works as expected if select "Wall" from drawer and then any of the "rollNumbers"
it is not working if i switch from one of the rollNumbers to other rollNumber in the drawer.
Similar to my first post. Only the _scaffoldTitle changes accordingly.
I'm new to Flutter,
I want to destruct cards created initially and construct them again as per data provided in API call.
Basically when I tap on button in UI, it should call APIs and based on data from API call, if it is different from the data I already have, I want to destruct cards and construct them again.
How I can achieve this?
The cards will auto update their content when you make the call again, it is like refreshing your data.
I have made a simple example with a single card that shows data from this JSON Where I am calling the API first time in initState and then repeating the call each time I press on the FAB.
I am adding the index variable just to show you the updates (updating my single card with the next item in the list)
Also it is worth noting that I am handling the null or empty values poorly for the sake of time.
Also forget about the UI overflow ¯_(ツ)_/¯
class CardListExample extends StatefulWidget {
#override
_CardListExampleState createState() => new _CardListExampleState();
}
class _CardListExampleState extends State<CardListExample> {
Map cardList = {};
int index = 0;
#override
void initState() {
_getRequests();
super.initState();
}
_getRequests() async {
String url = "https://jsonplaceholder.typicode.com/users";
var httpClinet = createHttpClient();
var response = await httpClinet.get(
url,
);
var data = JSON.decode(response.body);
//print (data);
setState(() {
this.cardList = data[index];
this.index++;
});
print(cardList);
print(cardList["name"]);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton:
new FloatingActionButton(onPressed: () => _getRequests()),
appBar: new AppBar(
title: new Text("Card List Example"),
),
body: this.cardList != {}
? new ListView(children: <Widget>[
new Card(
child: new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Text(
cardList["name"] ?? '',
style: Theme.of(context).textTheme.display1,
),
new Text(
this.cardList['email'] ?? '',
maxLines: 50,
),
],
),
new Text(cardList["website"] ?? '')
],
),
),
])
: new Center(child: new CircularProgressIndicator()),
);
}
}
Yes, Answer from Aziza works.
Though I used the code as below :
void main() =>
runApp(new MaterialApp(
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/about':
return new FromRightToLeft(
builder: (_) => new _aboutPage.About(),
settings: settings,
);
}
},
home : new HomePage(),
theme: new ThemeData(
fontFamily: 'Poppins',
primarySwatch: Colors.blue,
),
));
class HomePage extends StatefulWidget{
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage>{
List data;
Future<String> getData() async{
var response = await http.get(
Uri.encodeFull(<SOMEURL>),
headers: {
"Accept" : "application/json"
}
);
this.setState((){
data = JSON.decode(response.body);
});
return "Success";
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar : new AppBar(
title : new Text("ABC API"),
actions: <Widget>[
new IconButton( // action button
icon: new Icon(Icons.cached),
onPressed: () => getData(),
)],
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new Container(
height: 120.0,
child: new DrawerHeader(
padding: new EdgeInsets.all(0.0),
decoration: new BoxDecoration(
color: new Color(0xFFECEFF1),
),
child: new Center(
child: new FlutterLogo(
colors: Colors.blueGrey,
size: 54.0,
),
),
),
),
new ListTile(
leading: new Icon(Icons.chat),
title: new Text('Support'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/support');
}
),
new ListTile(
leading: new Icon(Icons.info),
title: new Text('About'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/about');
}
),
new Divider(),
new ListTile(
leading: new Icon(Icons.exit_to_app),
title: new Text('Sign Out'),
onTap: () {
Navigator.pop(context);
}
),
],
)
),
body: this.data != null ?
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index){
return new Container(
padding: new EdgeInsets.fromLTRB(8.0,5.0,8.0,0.0),
child: new Card(
child: new Padding(
padding: new EdgeInsets.fromLTRB(10.0,12.0,8.0,0.0),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
enabled: data[index]['active'] == '1' ? true : false,
title: new Text(data[index]['header'],
style:Theme.of(context).textTheme.headline,
),
subtitle: new Text("\n" + data[index]['description']),
),
new ButtonTheme.bar(
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: new Text(data[index]['action1']),
onPressed: data[index]['active'] == '1' ? _launchURL :null,
),
],
),
),
],
),
),
),
);
},
)
:new Center(child: new CircularProgressIndicator()),
);
}
}
_launchURL() async {
const url = 'http://archive.org';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
class FromRightToLeft<T> extends MaterialPageRoute<T> {
FromRightToLeft({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
return new SlideTransition(
child: new Container(
decoration: new BoxDecoration(
boxShadow: [
new BoxShadow(
color: Colors.black26,
blurRadius: 25.0,
)
]
),
child: child,
),
position: new Tween(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
)
.animate(
new CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
)
),
);
}
#override Duration get transitionDuration => const Duration(milliseconds: 400);
}
The above code includes Navigation drawer, page navigation animation and also answer to the above question.
The animation of ListView items that is about dragging to the left and the delete button appears works fine. The problem is that if I leave the delete button appearing and changing the page, when I return pop() to the previous page the button keeps appearing.
The animation did not go back to the beginning. The same happens if I update the items in the ListView, for example deleting an item. The item is deleted but the animation is not.
It looks like I'm updating the ListView content and not the ListView itself.
How could you solve this problem?
I'm trying to destroy ListView with every update in its items, but I'm not getting it. I also do not know if this is the right way to solve the problem.
The ListView is built through the buildTile() function and its items come from the database.
Below I put the code and a gif demonstrating what the problem is.
To work the code below, you need to insert the sqflite and path_provider dependencies into pubspec.yaml, thus:
dependencies:
sqflite: any
path_provider: any
flutter:
sdk: flutter
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
enum DialogOptionsAction {
cancel,
ok
}
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
routes: <String, WidgetBuilder> {
'/newpage': (BuildContext context) => new NewPage(),
},
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DatabaseClient _db = new DatabaseClient();
List listCategory = [];
List<Widget> tiles;
List colors = [
const Color(0xFFFFA500),
const Color(0xFF279605),
const Color(0xFF005959)
];
createdb() async {
await _db.create().then(
(data){
_db.getAllCategory().then((list){
setState(() {
this.listCategory = list;
});
});
}
);
}
#override
void initState() {
super.initState();
createdb();
}
void showCategoryDelete<T>({ BuildContext context, Widget child }) {
showDialog<T>(
context: context,
child: child,
)
.then<Null>((T value) {
if (value != null) {
setState(() { print(value); });
}
});
}
#override
Widget build(BuildContext context) {
List<Widget> buildTile(List list) {
this.tiles = [];
for(var dict in list) {
this.tiles.add(
new ItemCategory(
id: dict['id'],
category: dict['name'],
color: this.colors[dict['color']],
onPressed: () async {
showCategoryDelete<DialogOptionsAction>(
context: context,
child: new AlertDialog(
title: const Text('Delete Category'),
content: new Text(
'Do you want to delete this category?',
style: new TextStyle(
color: Colors.black26,
fontSize: 16.0,
fontFamily: "Roboto",
fontWeight: FontWeight.w500,
)
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}
),
new FlatButton(
child: const Text('OK'),
onPressed: () {
_db.deleteCategory(dict['id']).then(
(list) {
setState(() {
this.listCategory = list;
});
}
);
Navigator.pop(context);
}
)
]
)
);
},
)
);
}
return this.tiles;
}
return new Scaffold(
appBar: new AppBar(
title: new Text('Categories'),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.add),
color: new Color(0xFFFFFFFF),
onPressed: () async {
await Navigator.of(context).pushNamed('/newpage').then(
(data){
_db.getAllCategory().then((list){
setState(() {
this.listCategory = list;
});
});
}
);
}
)
],
),
body: new ListView(
padding: new EdgeInsets.only(top: 8.0, right: 0.0, left: 0.0),
children: buildTile(this.listCategory)
)
);
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('New Page'),
),
);
}
}
//Creating Database with some data and two queries
class DatabaseClient {
Database db;
Future create() async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
db = await openDatabase(dbPath, version: 1, onCreate: this._create);
}
Future _create(Database db, int version) async {
await db.execute("""
CREATE TABLE category (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
color INTEGER NOT NULL
)""");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo1', 0)");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo2', 1)");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo3', 2)");
}
Future getAllCategory() async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
Database db = await openDatabase(dbPath);
List list = await db.rawQuery('SELECT * FROM category');
await db.close();
return list;
}
Future deleteCategory(int id) async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
Database db = await openDatabase(dbPath);
await db.delete('category', where: "id = ?", whereArgs: [id]);
List list = await db.rawQuery('SELECT * FROM category');
await db.close();
return list;
}
}
//Creating ListViews items
class ItemCategory extends StatefulWidget {
ItemCategory({ Key key, this.id, this.category, this.color, this.onPressed}) : super(key: key);
final int id;
final String category;
final Color color;
final VoidCallback onPressed;
#override
ItemCategoryState createState() => new ItemCategoryState();
}
class ItemCategoryState extends State<ItemCategory> with TickerProviderStateMixin {
ItemCategoryState();
DatabaseClient db = new DatabaseClient();
AnimationController _controller;
Animation<double> _animation;
double flingOpening;
bool startFling = true;
void initState() {
super.initState();
_controller = new AnimationController(duration:
const Duration(milliseconds: 246), vsync: this);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
void _move(DragUpdateDetails details) {
final double delta = details.primaryDelta / 304;
_controller.value -= delta;
}
void _settle(DragEndDetails details) {
if(this.startFling) {
_controller.fling(velocity: 1.0);
this.startFling = false;
} else if(!this.startFling){
_controller.fling(velocity: -1.0);
this.startFling = true;
}
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
this.flingOpening = -(48.0/_width);
return new GestureDetector(
onHorizontalDragUpdate: _move,
onHorizontalDragEnd: _settle,
child: new Stack(
children: <Widget>[
new Positioned.fill(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: new Color(0xFFE57373),
),
child: new IconButton(
icon: new Icon(Icons.delete),
color: new Color(0xFFFFFFFF),
onPressed: widget.onPressed
)
),
],
),
),
new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: new Offset(this.flingOpening, 0.0),
).animate(_animation),
child: new Container(
decoration: new BoxDecoration(
border: new Border(
top: new BorderSide(style: BorderStyle.solid, color: Colors.black26),
),
color: new Color(0xFFFFFFFF),
),
margin: new EdgeInsets.only(top: 0.0, bottom: 0.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Container(
margin: new EdgeInsets.only(left: 16.0),
padding: new EdgeInsets.only(right: 40.0, top: 4.5, bottom: 4.5),
child: new Row(
children: <Widget>[
new Container(
margin: new EdgeInsets.only(right: 16.0),
child: new Icon(
Icons.brightness_1,
color: widget.color,
size: 35.0,
),
),
new Text(
widget.category,
style: new TextStyle(
color: Colors.black87,
fontSize: 14.0,
fontFamily: "Roboto",
fontWeight: FontWeight.w500,
),
),
],
)
)
],
),
)
],
),
)
),
],
)
);
}
}
According to the Flutter - Widget animation status remains even after it has been removed
Simply insert the key into the new ItemCategory (
I needed to pass a key to the children. Or else the renderer won't be able to know which SlideTransition got removed, and use the index.
...
#override
Widget build(BuildContext context) {
List<Widget> buildTile(List list) {
this.tiles = [];
for(var dict in list) {
this.tiles.add(
new ItemCategory(
key: new Key(dict), //new
id: dict['id'],
category: dict['name'],
...
The complete code is:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
enum DialogOptionsAction {
cancel,
ok
}
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
routes: <String, WidgetBuilder> {
'/newpage': (BuildContext context) => new NewPage(),
},
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DatabaseClient _db = new DatabaseClient();
List listCategory = [];
List<Widget> tiles;
List colors = [
const Color(0xFFFFA500),
const Color(0xFF279605),
const Color(0xFF005959)
];
createdb() async {
await _db.create().then(
(data){
_db.getAllCategory().then((list){
setState(() {
this.listCategory = list;
});
});
}
);
}
#override
void initState() {
super.initState();
createdb();
}
void showCategoryDelete<T>({ BuildContext context, Widget child }) {
showDialog<T>(
context: context,
child: child,
)
.then<Null>((T value) {
if (value != null) {
setState(() { print(value); });
}
});
}
#override
Widget build(BuildContext context) {
List<Widget> buildTile(List list) {
this.tiles = [];
for(var dict in list) {
this.tiles.add(
new ItemCategory(
key: new Key(dict), //new
id: dict['id'],
category: dict['name'],
color: this.colors[dict['color']],
onPressed: () async {
showCategoryDelete<DialogOptionsAction>(
context: context,
child: new AlertDialog(
title: const Text('Delete Category'),
content: new Text(
'Do you want to delete this category?',
style: new TextStyle(
color: Colors.black26,
fontSize: 16.0,
fontFamily: "Roboto",
fontWeight: FontWeight.w500,
)
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}
),
new FlatButton(
child: const Text('OK'),
onPressed: () {
_db.deleteCategory(dict['id']).then(
(list) {
setState(() {
this.listCategory = list;
});
}
);
Navigator.pop(context);
}
)
]
)
);
},
)
);
}
return this.tiles;
}
return new Scaffold(
appBar: new AppBar(
title: new Text('Categories'),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.add),
color: new Color(0xFFFFFFFF),
onPressed: () async {
await Navigator.of(context).pushNamed('/newpage').then(
(data){
_db.getAllCategory().then((list){
setState(() {
this.listCategory = list;
});
});
}
);
}
)
],
),
body: new ListView(
padding: new EdgeInsets.only(top: 8.0, right: 0.0, left: 0.0),
children: buildTile(this.listCategory)
)
);
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('New Page'),
),
);
}
}
//Creating Database with some data and two queries
class DatabaseClient {
Database db;
Future create() async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
db = await openDatabase(dbPath, version: 1, onCreate: this._create);
}
Future _create(Database db, int version) async {
await db.execute("""
CREATE TABLE category (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
color INTEGER NOT NULL
)""");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo1', 0)");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo2', 1)");
await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo3', 2)");
}
Future getAllCategory() async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
Database db = await openDatabase(dbPath);
List list = await db.rawQuery('SELECT * FROM category');
await db.close();
return list;
}
Future deleteCategory(int id) async {
Directory path = await getApplicationDocumentsDirectory();
String dbPath = join(path.path, "database.db");
Database db = await openDatabase(dbPath);
await db.delete('category', where: "id = ?", whereArgs: [id]);
List list = await db.rawQuery('SELECT * FROM category');
await db.close();
return list;
}
}
//Creating ListViews items
class ItemCategory extends StatefulWidget {
ItemCategory({ Key key, this.id, this.category, this.color, this.onPressed}) : super(key: key);
final int id;
final String category;
final Color color;
final VoidCallback onPressed;
#override
ItemCategoryState createState() => new ItemCategoryState();
}
class ItemCategoryState extends State<ItemCategory> with TickerProviderStateMixin {
ItemCategoryState();
DatabaseClient db = new DatabaseClient();
AnimationController _controller;
Animation<double> _animation;
double flingOpening;
bool startFling = true;
void initState() {
super.initState();
_controller = new AnimationController(duration:
const Duration(milliseconds: 246), vsync: this);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
void _move(DragUpdateDetails details) {
final double delta = details.primaryDelta / 304;
_controller.value -= delta;
}
void _settle(DragEndDetails details) {
if(this.startFling) {
_controller.fling(velocity: 1.0);
this.startFling = false;
} else if(!this.startFling){
_controller.fling(velocity: -1.0);
this.startFling = true;
}
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
this.flingOpening = -(48.0/_width);
return new GestureDetector(
onHorizontalDragUpdate: _move,
onHorizontalDragEnd: _settle,
child: new Stack(
children: <Widget>[
new Positioned.fill(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: new Color(0xFFE57373),
),
child: new IconButton(
icon: new Icon(Icons.delete),
color: new Color(0xFFFFFFFF),
onPressed: widget.onPressed
)
),
],
),
),
new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: new Offset(this.flingOpening, 0.0),
).animate(_animation),
child: new Container(
decoration: new BoxDecoration(
border: new Border(
top: new BorderSide(style: BorderStyle.solid, color: Colors.black26),
),
color: new Color(0xFFFFFFFF),
),
margin: new EdgeInsets.only(top: 0.0, bottom: 0.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Container(
margin: new EdgeInsets.only(left: 16.0),
padding: new EdgeInsets.only(right: 40.0, top: 4.5, bottom: 4.5),
child: new Row(
children: <Widget>[
new Container(
margin: new EdgeInsets.only(right: 16.0),
child: new Icon(
Icons.brightness_1,
color: widget.color,
size: 35.0,
),
),
new Text(
widget.category,
style: new TextStyle(
color: Colors.black87,
fontSize: 14.0,
fontFamily: "Roboto",
fontWeight: FontWeight.w500,
),
),
],
)
)
],
),
)
],
),
)
),
],
)
);
}
}