UPDATE
I have changed to use a widget and put all the code for the Stateful Widget below.
There is lots of code to this, I changed to use a stack with a Widget loadingIndicator, but it still does not get called. My stack is around a future response, with the indicator at the bottom, I then use the onPress from a button click to call a method that changes state and then I call the other process to run. Still nothing happens with the indicator.
class ChatServerDivided extends StatefulWidget {
ChatServerDivided({Key key, this.title, this.mychat}) : super(key: key);
static const String routeName = "/ChatServerDivided";
final ChatServerList mychat;
final String title;
#override
_ChatServerDividedState createState() => new _ChatServerDividedState();
}
class _ChatServerDividedState extends State<ChatServerDivided> {
SharedPreferences prefs;
int oid = 0;
int pid = 0;
int authlevel = 0;
bool admin = false;
int type = 0;
String msgid = '';
List chatlist;
int listcount = 0;
bool grpmsg = true;
String sender = '';
String receiver = '';
String message = '';
String oname = '';
String pname = '';
String sendname;
String receivename;
String replyto = '';
String replyfrom = '';
String replysub = '';
final TextEditingController _newreplycontroller = new TextEditingController();
String myfcmtoken = 'NONE';
static ScrollController _scrollController;
bool messSync = false;
//Future<http.Response> _responseFuture;
Future<List<Map>> _responseFuture;
var _urlDates = '';
Future<File> _imageFile;
String myimage;
String myvideo;
File myimagefile;
File myvidfile;
Future<int> myimagelength;
String myext;
VideoPlayerController vcontroller;
bool isImage = false;
bool isVideo = false;
bool submitting = false;
//ScrollController scontroller = new ScrollController();
_getPrefs() async {
prefs = await SharedPreferences.getInstance();
if (mounted) {
setState(() {
oid = prefs.getInt('oid');
pid = prefs.getInt('pid');
authlevel = prefs.getInt('authlevel');
admin = prefs.getBool('admin');
type = 1;
msgid = widget.mychat.msgkey;
if (widget.mychat.grpid == 0) {
grpmsg = false;
} else {
grpmsg = true;
}
oname = widget.mychat.oname;
pname = widget.mychat.pname;
myfcmtoken = prefs.getString('fcmtoken');
if (authlevel == 0) {
sender = 'o';
receiver = 'p';
sendname = widget.mychat.oname;
receivename = widget.mychat.pname;
} else if (authlevel == 1) {
sender = 'p';
receiver = 'o';
sendname = widget.mychat.pname;
receivename = widget.mychat.oname;
}
//_getChats();
});
}
}
#override
void initState() {
super.initState();
//controller = new TabController(length: 4, vsync: this);
_scrollController = new ScrollController();
//_scrollController.position.maxScrollExtent;
_getPrefs();
_urlDates =
'http://$baseurl/chat/messages/getdates/${widget
.mychat.msgkey}';
_responseFuture = ChatDB.instance.getMessagesDates(widget.mychat.msgkey);
}
#override
void dispose() {
super.dispose();
if (vcontroller != null) {
vcontroller.dispose();
}
}
var jsonCodec = const JsonCodec();
var _focusnode = new FocusNode();
_getChats() async {
var _url =
'http://$baseurl/chat/messages/getdates/$msgid';
var http = createHttpClient();
var response = await http.get(_url, headers: getAuthHeader());
var chats = await jsonCodec.decode(response.body);
if (mounted) {
setState(() {
chatlist = chats.toList();
listcount = chatlist.length;
//replysub = 'Re: ' + chatlist[0]['sub'];
});
}
}
Future<Null> _onRefresh() {
Completer<Null> completer = new Completer<Null>();
Timer timer = new Timer(new Duration(seconds: 1), () {
setState(() {
_responseFuture =
ChatDB.instance.getMessagesDates(widget.mychat.msgkey);
print('RUNNING LOAD AFTER REFRESH AGAIN');
});
completer.complete();
});
return completer.future;
}
Future<String> doImageString() async {
return (await _imageFile)
.path
.substring((await _imageFile).path.length - 3);
}
#override
Widget build(BuildContext context) {
_toggleProgress() {
setState(() {
submitting = true;
});
};
Widget loadingIndicator =submitting? new Container(
color: Colors.grey[300],
width: 70.0,
height: 70.0,
child: new Padding(padding: const EdgeInsets.all(5.0),child: new Center(child: new CircularProgressIndicator())),
):new Container();
Widget mytitle;
if (grpmsg) {
mytitle = new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Icon(Icons.people),
new Text(' '),
new Text(widget.mychat.referralname)
],
);
} else {
mytitle = new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Icon(Icons.person),
new Text(' '),
new Text(widget.mychat.referralname)
],
);
}
var _children = <Widget>[
new Flexible(
child: new Stack(
children: <Widget>[
new FutureBuilder<List<Map>>(
future: _responseFuture,
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('Loading...');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
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
);
}
}
},
),
new Align(child: loadingIndicator,alignment: FractionalOffset.center,),
],
),
),
new Container(
alignment: Alignment.bottomLeft,
padding: new EdgeInsets.only(left: 10.0),
child: new FutureBuilder<File>(
future: _imageFile,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
//return new Image.file(snapshot.data);
myimagefile = snapshot.data;
myext = path.extension(myimagefile.path);
if (myext == '.jpg') {
isImage = true;
return new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
alignment: Alignment.bottomLeft,
width: 150.0,
child: new Image.file(snapshot.data),
),
new FlatButton(
onPressed: _doClear,
child: new Text('Clear Image'))
],
);
} else {
isVideo = true;
myvidfile = new File(
snapshot.data.path.replaceAll('file://', ''));
vcontroller = new VideoPlayerController(myimagefile.path)..initialize();
return new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
alignment: Alignment.bottomLeft,
width: 150.0,
child: new vplayer.VideoCard(
controller: vcontroller,
title: widget.mychat.referralname,
subtitle: 'Video',
),
),
new FlatButton(
onPressed: _doClear,
child: new Text('Clear Video'))
],
);
}
} else {
return const Text('');
}
})
),
new Divider(
height: 5.0,
color: Colors.grey,
),
new Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new Container(
alignment: Alignment.bottomLeft,
//width: 50.0,
child: new IconButton(
icon: new Icon(Icons.add_a_photo),
onPressed: _pickImage,
alignment: Alignment.bottomLeft,
),
),
new Flexible(
child: new Container(
alignment: Alignment.center,
//width: 350.0,
child: new TextField(
decoration: const InputDecoration(
hintText: 'Reply',
labelText: 'Reply:',
),
autofocus: false,
focusNode: _focusnode,
maxLines: 1,
controller: _newreplycontroller,
keyboardType: TextInputType.text,
),
),
),
new Container(
alignment: Alignment.bottomRight,
//width: 50.0,
child: new IconButton(
icon: new Icon(Icons.send),
onPressed: () {
_toggleProgress();
_sendReply();
},
alignment: Alignment.centerRight,
disabledColor: Colors.grey,
)),
],
),
];
return new Scaffold(
appBar: new AppBar(
title: mytitle,
actions: getAppBarActions(context),
),
body: new Column(
children: _children,
),
);
}
DateTime getDateDiv(int index) {
DateTime msgdate = DateTime.parse(chatlist[index]['chatdate']).toLocal();
return msgdate;
}
_doClear() {
setState(() {
_imageFile = null;
});
}
_pickImage() async {
await setState(() {
_imageFile = ImagePicker.pickImage(maxWidth: 600.0);
});
}
_sendReply() {
if (_newreplycontroller.text.isEmpty && myimagefile == null) {
/*showDialog(
context: context,
child: new AlertDialog(
content: new Text("There is no message to submit"),
actions: <Widget>[
new FlatButton(
child: const Text('OK'),
onPressed: () {
Navigator.pop(context, false);
}),
],
),
);*/
} else {
TextInputAction.done;
DateTime dateSubmit = new DateTime.now();
if (myimagefile != null) {
if (isImage) {
List<int> imageBytes = myimagefile.readAsBytesSync();
myimage = BASE64.encode(imageBytes);
myvideo = 'NONE';
}
if (isVideo) {
List<int> imageBytes = myvidfile.readAsBytesSync();
myvideo = BASE64.encode(imageBytes);
myimage = 'NONE';
}
} else {
myimage = 'NONE';
myvideo = 'NONE';
}
var mymessage = _newreplycontroller.text;
ChatServerMessage mychat = new ChatServerMessage(
widget.mychat.msgkey,
'message',
widget.mychat.refid,
widget.mychat.referralname,
replysub,
oid,
oname,
pid,
pname,
sender,
sendname,
receiver,
receivename,
mymessage,
dateSubmit.toString(),
widget.mychat.grpid.toString(),
widget.mychat.prid.toString(),
myfcmtoken,
myimage,
myvideo,
myext);
_doSendReply(mychat);
}
}
_doSendReply(mychat) async {
var json = jsonCodec.encode(mychat);
ChatConnect.instance.sendmessage(json);
_checkSync () {
messSync = ChatConnect.instance.isSynced;
if (messSync) {
if (isImage) {
Timer synctimer = new Timer(new Duration(seconds: 2), _checkSync);
} else if (isVideo) {
Timer synctimer = new Timer(new Duration(seconds: 5), _checkSync);
} else {
Timer synctimer = new Timer(new Duration(seconds: 2), _checkSync);
}
} else {
setState(() {
submitting = false;
_onRefresh();
_doClear();
print('RUNNING LOAD AFTER SEND AGAIN');
});
_newreplycontroller.text = '';
_focusnode.unfocus();
}
}
_checkSync();
}
}
I had Created a example based on your query
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new sample()));
}
class sample extends StatefulWidget {
#override
_sampleState createState() => new _sampleState();
}
class _sampleState extends State<sample> {
bool _progressBarActive = true;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar:new AppBar(
title: new Text("Circular progressbar demo"),
),
body: _progressBarActive == true?const CircularProgressIndicator():new Container());
}
}
I created an example based on your code snippet above. Black app with an button to toggle the submitting value using setState().
Tapping the FloatingActionButton calls setState, toggling the value of submitting. And the progress indicator is shown.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new SubmitPage()));
}
class SubmitPage extends StatefulWidget {
#override
_SubmitPageState createState() => new _SubmitPageState();
}
class _SubmitPageState extends State<SubmitPage> {
bool submitting = false;
void toggleSubmitState() {
setState(() {
submitting = !submitting;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: !submitting
? new Container(
color: Colors.grey,
)
: const Center(child: const CircularProgressIndicator()),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.refresh),
onPressed: toggleSubmitState,
),
);
}
}
Why don't you use .gif image instead?
Simply place your gif in your images folder of your project and mention it in pubspec.yaml file,just like you do for images.
Image(
image: AssetImage('images/circular-progress-bar.gif'),
width: 40,
height:40
);
Related
I' m new in Flutter. I made an app that has a catalogue with categories, subcategories and items (as you can see in photo). My widget tree is:
Catalogue(cart):{[Category-List, Subcategory-List, Item-List:{ItemRow(ListTile)}]}.
I'm facing the following problem: I have items in my cart and their quantity is shown on the catalogue (item rows). When I delete an item from the cart, or clear all items, I can't set the controller of the text field of the item Row to zero cause that widget (current item row) has been mounted. I use Scoped model to add, delete or update items in the cart. So, my problem is just visual. When I click on another category and then go to the previous, the controller has been set to zero correctly (cause the item rows recreated again with initstate()).
Is there any solution to my problem? Thanks!
App Image:
Catalogue.dart Code:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import '../widgets/categories/categories_manager.dart';
import '../widgets/subcats/subcat_manager.dart';
import '../widgets/items/items_list.dart';
import '../scoped-models/main.dart';
import '../models/item.dart';
class CataloguePage extends StatefulWidget {
final String _langSelected;
CataloguePage(this._langSelected, this.model);
final MainModel model;
#override
State<StatefulWidget> createState() {
return _CataloguePageState();
}
}
class _CataloguePageState extends State<CataloguePage> {
Widget currentPage;
SubcatManager subcatPage;
bool _loadingProgress;
List<Item> _listCart;
final SlidableController slidableController = SlidableController();
#override
void initState() {
_listCart = widget.model.itemsInCart;
_loadingProgress = true;
widget.model
.fetchCategories(widget.model.serverAddress, widget.model.serverPort)
.then((bool success) {
if (success) {
setState(() {
widget.model
.fetchSubcats(widget.model.serverAddress, widget.model.serverPort,
widget.model.categories[0].catid)
.then((bool success2) {
if (success2) {
setState(() {
widget.model
.fetchItems(
widget.model.serverAddress,
widget.model.serverPort,
widget.model.categories[0].catid,
widget.model.subcats[0].subcatid)
.then((bool success3) {
if (success3) {
_loadingProgress = false;
}
});
});
}
});
});
} else {
showDialog(
barrierDismissible: true,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An error has occured.'),
content: Text('Connection with Server failed!'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.popUntil(
context, (_) => !Navigator.canPop(context));
Navigator.pushReplacementNamed(context, '/');
},
child: Text('OK'),
)
],
);
});
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColor,
title: Text('Catalogue'),
actions: <Widget>[
Stack(
children: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
size: 30.0,
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext contex) {
return _buildCartList(_listCart);
},
);
},
),
widget.model.itemsInCart.length > 0
? CircleAvatar(
radius: 10.0,
child: Text(widget.model.itemsInCart.length.toString()),
)
: Container()
],
)
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_loadingProgress) {
return Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Theme.of(context).platform == TargetPlatform.iOS
? CupertinoActivityIndicator(
radius: 20.0,
)
: CircularProgressIndicator(
strokeWidth: 3.0,
),
),
);
} else {
return Container(
padding: EdgeInsets.all(20),
color: Theme.of(context).backgroundColor,
child: Row(
children: <Widget>[
Flexible(
flex: 3,
child: Column(
children: [
Flexible(
child: CategoriesManager(widget.model),
),
],
),
),
widget.model.subcats[0].subcatid == 0
? Container()
: VerticalDivider(
color: widget.model.themeBrightness == 1
? Colors.white
: Colors.black,
),
widget.model.subcats[0].subcatid == 0
? Container()
: Flexible(
flex: 3,
child: Column(
children: [
Flexible(
child: SubcatManager(widget.model),
),
],
),
),
VerticalDivider(
color: widget.model.themeBrightness == 1
? Colors.white
: Colors.black,
),
Flexible(
flex: 4,
child: Column(
children: [
Text('Items'),
SizedBox(
height: 20.0,
),
Flexible(
child: ItemList(widget.model),
),
],
),
),
],
),
);
}
}
Widget _buildCartList(List<Item> listCart) {
Widget itemCartCards;
if (listCart.length > 0) {
itemCartCards = Padding(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Selected Items'),
Text('Total Quantity: ' +
widget.model.cartTotalItems.toString()),
],
),
SizedBox(
height: 10.0,
),
Expanded(
child: ListView.separated(
separatorBuilder: (contex, index) => Divider(
color: widget.model.themeBrightness == 1
? Colors.white
: Colors.black,
),
itemBuilder: (BuildContext context, index) {
return Slidable(
key: Key(listCart[index].itemid),
controller: slidableController,
delegate: SlidableDrawerDelegate(),
actionExtentRatio: 0.25,
secondaryActions: <Widget>[
IconSlideAction(
icon: Icons.delete,
caption: 'Delete',
color: Colors.red,
onTap: () {
widget.model
.deleteItemFromCart(listCart[index].itemid);
},
)
],
child: ListTile(
title: Text(
listCart[index].itemperi,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
widget.model.showListItemsPrices
? Text(listCart[index].itemCount.toString() +
' x ' +
listCart[index].itemprice.toString() +
' €')
: Text(listCart[index].itemCount.toString()),
],
),
),
);
},
itemCount: listCart.length,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Text('Clear Order'),
color: Colors.red,
onPressed: () {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
title: Text('Warning!'),
content:
Text('Are you sure you want to empty your cart?'),
actions: <Widget>[
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
widget.model.deleteAllCartItems();
}),
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
),
SizedBox(
width: 20.0,
),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Text('Confirm Order'),
color: Colors.green,
onPressed: () {
_buildJsonOrder();
},
),
],
),
],
),
);
} else {
itemCartCards = Container(
child: Center(
child: Text('Your cart is empty.'),
),
);
}
return itemCartCards;
}
void _buildJsonOrder() {
final List<dynamic> _listItems = [];
for (Item item in widget.model.itemsInCart) {
final Map<String, dynamic> itemData = {
'hallid': [widget.model.hallNumber],
'tableid': [widget.model.tableNumber],
'itemid': ['${item.itemid}'],
'itemperi': ['${item.itemperi}'],
'kind': [0],
'catid': [item.itemCatId],
'subcatid': [item.itemSubcatId],
'quantity': [item.itemCount],
'price': [item.itemprice]
};
_listItems.add(itemData);
}
final Map<String, dynamic> orderData = {
'hallid': [widget.model.hallNumber],
'tableid': [widget.model.tableNumber],
'typeofpos': ['4'],
'posid': [600],
'userid': [widget.model.currentUserId],
'items': _listItems
};
widget.model
.sendOrder(
widget.model.serverAddress, widget.model.serverPort, orderData)
.then((bool success) {
if (success) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Success.'),
content: Text('Your order has been placed successfully!'),
actions: <Widget>[
FlatButton(
onPressed: () {
widget.model.deleteAllCartItems();
Navigator.pop(context);
Navigator.of(context).pop();
},
child: Text('OK'),
)
],
);
});
} else {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An error has occured.'),
content: Text('Something went wrong with your order.'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('OK'),
)
],
);
});
}
});
}
}
ItemList.dart Code:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import '../../models/item.dart';
import '../../scoped-models/main.dart';
import './item_row.dart';
class ItemList extends StatefulWidget {
final MainModel model;
ItemList(this.model);
#override
State<StatefulWidget> createState() {
return _ItemListState();
}
}
class _ItemListState extends State<ItemList> {
#override
void initState() {
super.initState();
}
Widget _buildItemList(List<Item> items) {
Widget itemCards;
if (items.length > 0) {
itemCards = ListView.separated(
separatorBuilder: (contex, index) => Divider(
color: widget.model.themeBrightness == 1
? Colors.white
: Colors.black,
),
itemBuilder: (BuildContext context, index) {
return ItemRow(widget.model, items[index]);
},
itemCount: items.length,
);
} else {
itemCards = Container();
}
return itemCards;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return _buildItemList(model.items);
},
),
),
],
);
}
}
ItemRow.dart Code:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import '../../models/item.dart';
import '../../scoped-models/main.dart';
class ItemRow extends StatefulWidget {
final MainModel model;
final Item item;
ItemRow(this.model, this.item);
#override
State<StatefulWidget> createState() {
return _ItemRowState();
}
}
class _ItemRowState extends State<ItemRow> {
int _itemCount;
TextEditingController _itemCountController;
#override
void initState() {
setState(() {
_itemCount = widget.item.itemCount;
_itemCountController = TextEditingController(text: _itemCount.toString());
});
super.initState();
}
#override
void didUpdateWidget(ItemRow oldWidget) {
if (oldWidget.item.itemid != widget.item.itemid) {
setState(() {
_itemCount = widget.item.itemCount;
_itemCountController =
TextEditingController(text: _itemCount.toString());
});
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return _buildItem(widget.item);
},
);
}
Widget _buildItem(Item item) {
return ListTile(
leading: CircleAvatar(
backgroundImage: item.itemimage == ''
? AssetImage('assets/noimage.png')
: NetworkImage(item.itemimage)),
title: Text(
item.itemperi,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
widget.model.showListItemsPrices
? Text(
item.itemprice.toString() + ' €',
)
: Container(),
widget.model.showListItemsCart ? VerticalDivider() : Container(),
widget.model.showListItemsCart
? _buildListItemCart(item)
: Container()
],
),
onTap: () {
if (widget.model.clickItems) {
Navigator.pushNamed(context, '/item/' + item.itemid);
}
},
);
}
Widget _buildListItemCart(Item item) {
return Container(
child: Row(
children: <Widget>[
GestureDetector(
onLongPress: () {
if (_itemCount != 0) {
setState(() {
_itemCount = 0;
_itemCountController =
TextEditingController(text: _itemCount.toString());
widget.model.deleteItemFromCart(item.itemid);
});
}
},
child: IconButton(
icon: Icon(Icons.remove),
onPressed: () {
if (_itemCount != 0) {
setState(() {
_itemCount--;
_itemCountController =
TextEditingController(text: _itemCount.toString());
if (_itemCount == 0) {
widget.model.deleteItemFromCart(item.itemid);
} else {
widget.model.updateItemCart(item.itemid, _itemCount);
}
});
}
},
),
),
Container(
width: 30.0,
child: TextField(
decoration: InputDecoration(border: InputBorder.none),
textAlign: TextAlign.center,
controller: _itemCountController,
keyboardType: TextInputType.numberWithOptions(),
onTap: () {
_itemCountController.selection = TextSelection(
baseOffset: 0,
extentOffset: _itemCountController.text.length);
},
onSubmitted: (value) {
if (value == 0.toString()) {
widget.model.deleteItemFromCart(item.itemid);
}
if (value != 0.toString() && _itemCount == 0) {
widget.model.addItemToCart(
item.itemid,
item.itemperi,
item.itemprice,
int.parse(value),
int.parse(widget.model.selectedCatid),
int.parse(widget.model.selectedSubcatId));
}
if (value != 0.toString() && _itemCount != 0) {
widget.model.updateItemCart(item.itemid, int.parse(value));
}
_itemCount = int.parse(value);
TextEditingController(text: _itemCount.toString());
},
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(
() {
_itemCount++;
_itemCountController =
TextEditingController(text: _itemCount.toString());
if (_itemCount == 1) {
widget.model.addItemToCart(
item.itemid,
item.itemperi,
item.itemprice,
_itemCount,
int.parse(widget.model.selectedCatid),
int.parse(widget.model.selectedSubcatId));
} else {
widget.model.updateItemCart(item.itemid, _itemCount);
}
},
);
},
),
],
),
);
}
}
ScopedModelItems.dart Code:
import 'package:scoped_model/scoped_model.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/item.dart';
import '../scoped-models/main.dart';
mixin ItemsModel on Model {
MainModel model;
List<Item> _items = [];
List<Item> itemsEmpty = [];
int _cartTotalItems = 0;
List<Item> _itemsCartEmpty = [];
Item _itemDetails;
Item get itemDetails {
return _itemDetails;
}
List<Item> get items {
return List.of(_items);
}
List<Item> get itemsInCart {
return _itemsCartEmpty;
}
int get cartTotalItems {
if (_cartTotalItems == null) {
_cartTotalItems = 0;
}
return _cartTotalItems;
}
void initState() {
_items.forEach(_addItem);
}
void _addItem(Item item) {
itemsEmpty.add(item);
}
void addItemToCart(String itemid, String itemperi, num itemprice,
int itemQuantity, int itemCatid, int itemSubcatid) {
Item item = Item(
itemid: itemid,
itemperi: itemperi,
itemprice: itemprice,
itemCount: itemQuantity,
itemCatId: itemCatid,
itemSubcatId: itemSubcatid);
_itemsCartEmpty.add(item);
_cartTotalItems += itemQuantity;
notifyListeners();
}
void updateItemCart(String itemid, int itemQuantity) {
_itemsCartEmpty.forEach((item) {
if (item.itemid == itemid) {
_cartTotalItems -= item.itemCount;
item.itemCount = itemQuantity;
_cartTotalItems += itemQuantity;
}
});
notifyListeners();
}
void deleteItemFromCart(String itemid) {
_itemsCartEmpty.forEach((item) {
if (item.itemid == itemid) {
_cartTotalItems -= item.itemCount;
}
});
_itemsCartEmpty.removeWhere((item) => item.itemid == itemid);
notifyListeners();
}
void deleteAllCartItems() {
_itemsCartEmpty.removeRange(0, _itemsCartEmpty.length);
_cartTotalItems = 0;
notifyListeners();
}
Future<bool> fetchItems(String serverAddress, String serverPort,
dynamic catid, dynamic subcatid) {
return http.get(
'http://$serverAddress:$serverPort/cats/$catid/subcats/$subcatid/items/GR',
headers: {'Accept': 'application/json'}).then((http.Response response) {
if (response.statusCode == 200 || response.statusCode == 201) {
final List<Item> fetchedItemList = [];
final List<dynamic> itemListData = json.decode(response.body);
itemListData.forEach((dynamic itemData) {
String imageData = '', periData = '';
if (itemData['item_image_path'] != '') {
imageData = 'http://$serverAddress:$serverPort/photos/' +
itemData['item_image_path'];
}
if (itemData['item_webperi'] == '') {
periData = itemData['item_peri'];
} else {
periData = itemData['item_webperi'];
}
final Item item = Item(
itemid: itemData['item_id'],
itemperi: periData,
itemimage: imageData,
itemprice: itemData['item_price'],
itemCount: 0);
if (_itemsCartEmpty.isNotEmpty) {
for (Item itemCart in _itemsCartEmpty) {
if (itemCart.itemid == item.itemid) {
item.itemCount = itemCart.itemCount;
}
}
}
fetchedItemList.add(item);
});
_items = fetchedItemList;
notifyListeners();
return true;
} else {
return false;
}
}).catchError((error) {
print(error);
notifyListeners();
return false;
});
}
}
Since there's no code I'll just list the common errors that cause this when using scoped model:
1. Not calling notify
You have to call notifyListeners() in your scoped model to update the UI that's using your porperties.
2. Not telling model to rebuild
If you're not using a ScopedModelDescendent widget and instead you're getting your model using
appModel = ScopedModel.of<AppModel>(context);
change your code to
appModel = ScopedModel.of<AppModel>(context, rebuildOnChange: true);
If you're doing both of these then please post your code for your UI and scoped model so that it's easier to offer our help.
I'm facing some issues quite the time lately. I'm using the scoped_model package for my application. But i'm getting the problem that when i'm searching for stuff or i turn my screen to landscape modus it builds twice.
When this happens i'm getting duplicate json because it is rebuilding twice.
I'm getting another issue when i have searched and i go back my custom scroll view is not working anymore. I can't scroll down anymore.
I have writtin the following files:
main.dart:
class GetRelations extends StatefulWidget {
#override
RelationsPage createState() => RelationsPage();
}
class RelationsPage extends State<GetRelations> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
#override
Widget build(BuildContext context) {
relationScopedModel.initializeValues();
return Scaffold(
body: ScopedModel<RelationScopedModel>(
model: relationScopedModel,
child: Container(
child: SearchScreen(),
),
),
);
}
}
search_screen.dart:
class SearchScreen extends StatefulWidget {
#override
SearchScreenState createState() {
return new SearchScreenState();
}
}
class SearchScreenState extends State<SearchScreen> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
ScrollController controller;
int page = 0;
bool atEnd = false;
#override
void initState() {
super.initState();
controller = new ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
void _scrollListener() {
var props = RelationScopedModel.of(context);
/// When reload we check if skip == 0, if skip is 0 then page has to become 0 too.
if (props.skip == 0) {
page = 0;
}
/// Checking if user is at the end of the screen, then let's receive some new content.
if (controller.position.pixels == controller.position.maxScrollExtent) {
/// If it is at the end, we have to set atEnd to true.
if (props.atTheEnd == true) {
atEnd = props.atTheEnd;
return;
}
/// If it has no more pages, return.
if (props.hasMorePages == false) {
return;
}
/// If it is has more stuff, load it in!
if (!props.isLoadingMore && props.hasMorePages) {
page++;
props.getRelations(props.search, page);
}
}
}
#override
Widget build(BuildContext context) {
/// Go back to last page by using WillPopScope.
return WillPopScope(
onWillPop: () {
Navigator.pop(context);
},
child: new Scaffold(
drawer: new DrawerOnly(),
/// We are using Scoped Model to load the json in the sliver list.
body: ScopedModelDescendant<RelationScopedModel>(
builder: (context, child, model) {
return CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverAppBar(
title: SearchWidget(
performSearch: model.getRelations,
),
floating: true,
pinned: true,
),
model.isLoading && model.atTheEnd
? SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
)
: model.getRelationCount() < 1
? SliverFillRemaining(
child: Center(
child: Text(
model.statusText,
style: Theme.of(context).textTheme.headline,
),
),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == model.getRelationCount() + 1) {
if (model.hasMorePages == true) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0),
child: Center(
child: CircularProgressIndicator()),
);
}
return Container(width: 0, height: 0);
} else if (index == 0) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey[300]))),
child: Text(
"Relaties",
style: Theme.of(context)
.textTheme
.body2
.copyWith(color: Colors.white),
),
);
} else {
return Container(
child: Column(
children: <Widget>[
InkWell(
child: RelationItem(
model.relation[index - 1]),
onTap: () {
var id =
model.relation[index - 1].id;
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext
context) =>
new DetailScreen(id)));
},
),
],
),
);
}
},
childCount: model.getRelationCount() + 2,
),
)
],
);
},
),
),
);
}
}
search.dart (this is my searchwidget):
class SearchWidget extends StatelessWidget { final performSearch;
const SearchWidget({Key key, #required this.performSearch}) : super(key: key);
#override Widget build(BuildContext context) {
print('wat gebeurt er');
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
GestureDetector(
child: Icon(
Icons.search,
color: Colors.white,
),
onTap: () {},
),
SizedBox(
width: 10.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
hintStyle: TextStyle(fontSize: 14.0, color: Colors.white),
border: InputBorder.none,
hintText: "Zoeken..."),
onChanged: (String search) {
if (search.length > 2) {
performSearch(search);
}
if (search.length == 0) {
performSearch(search);
}
},
),
),
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SettingsScreen()));
},
child: Icon(
FontAwesomeIcons.slidersH,
color: Colors.white,
),
),
],
),
),
); } }
relation_scoped_model.dart:
class RelationScopedModel extends Model {
List<GetRelation> _relation = [];
List<GetContacts> _contacts = [];
List<GetLocations> _locations = [];
bool _isLoading = false;
String _statusText = "Start met zoeken...";
bool _hasMorePages = true;
int _skip = 0;
int skipMore = 0;
String _search;
bool _isLoadingMore = false;
bool atTheEnd = false;
List<Map<String, String>> _listingTypeList = [
{"name": "Buy", "value": "buy"},
{"name": "Rent", "value": "rent"},
];
String _listingType;
List<Map<String, String>> _sortList = [
{"name": "Relevancy", "value": "relevancy"},
{"name": "Bedroom (Ascending)", "value": "bedroom_lowhigh"},
{"name": "Bedroom (Descending)", "value": "bedroom_highlow"},
{"name": "Price (Ascending)", "value": "price_lowhigh"},
{"name": "Price (Descending)", "value": "price_highlow"},
{"name": "Newest", "value": "newest"},
{"name": "Oldest", "value": "oldest"},
{"name": "Random", "value": "random"},
{"name": "Distance", "value": "distance"}
];
String _sort;
List<GetRelation> get relation => _relation;
List<GetContacts> get contacts => _contacts;
List<GetLocations> get locations => _locations;
bool get isLoading => _isLoading;
String get statusText => _statusText;
bool get hasMorePages => _hasMorePages;
String get search => _search;
int get skip => _skip;
bool get isLoadingMore => _isLoadingMore;
List<Map<String, String>> get listingTypeList => _listingTypeList;
String get listingType => _listingType;
List<Map<String, String>> get sortList => _sortList;
String get sort => _sort;
int getRelationCount() => _relation.length;
void initializeValues() async {
_relation.clear();
SharedPreferences prefs = await SharedPreferences.getInstance();
_listingType = prefs.getString('listingType') ?? 'rent';
_sort = prefs.getString('sort') ?? 'relevancy';
getRelations(search);
}
var _skiptotal = 10;
var lastSearch;
Future<dynamic> _getData(String search, [int page = 0]) async {
/// When page is 0 we don't have to skip content, if page is 1 or higher it will have to skip content
if (page != 0) {
var skipPage = page * _skiptotal;
_skip = skipPage;
}
/// If page == 0, we have to set page and _skip to 0.
else {
_skip = 0;
page = 0;
}
/// When nothing is filled in the input search, we have to set search to single quotes because otherwise it will search on null.
if (search == null) {
search = '';
}
if (lastSearch != search) {
_relation.clear();
lastSearch = '';
lastSearch = search;
page + 1;
}
String _credentials;
SharedPreferences pref = await SharedPreferences.getInstance();
_credentials = (pref.getString("credentials") ?? "Empty");
var res = await http.get(
Uri.encodeFull("$cf_api_RelationsUrl" +
"?$cf_api_SkipParameter=$_skip&$cf_api_SearchParameter=$search&$cf_api_LimitParameter=10"),
headers: {
"content-type": "application/json",
"accept": "application/json",
'cookie': '$_credentials'
});
var decodedJson = json.decode(res.body, reviver: (k, v) {
if (k == "status" && v == false) {
_hasMorePages = false;
return v;
} else {
return v;
}
});
if (_hasMorePages == false) {
return decodedJson;
} else {
List list = List();
list = json.decode(res.body) as List;
if (list.length < 10) {
atTheEnd = true;
_hasMorePages = false;
return decodedJson;
}
}
return decodedJson;
}
Future getRelations(String search, [int page = 0]) async {
if (page == 0) {
page = 0;
_isLoading = true;
_relation.clear();
} else {
_isLoadingMore = true;
}
_search = search;
var responseData = await _getData(search, page);
notifyListeners();
var result = responseData
.map(
(data) => serializers.deserializeWith(GetRelation.serializer, data))
.toList();
result.forEach((relations) {
_relation.add(relations);
});
if (result.isEmpty) {
_statusText = "Nothing Found";
}
if (page == 0) {
_isLoading = false;
} else {
_isLoadingMore = false;
}
if (atTheEnd == true) {
return true;
}
// notifyListeners();
}
void setListingType(String value) async {
_listingType = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('listingType', _listingType);
}
void setSort(String value) async {
_sort = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('sort', _sort);
}
/// Wraps [ScopedModel.of] for this [Model].
static RelationScopedModel of(BuildContext context) =>
ScopedModel.of<RelationScopedModel>(context);
}
I'm guessing that the problem is that my scopedmodeldescendant isn't in the init state. But i'm not sure and i hope somebody knows what the problem is with this code.
Thanks in advance
Greetings,
Jente
UPDATE:
I got my customscrollview is working when i set atTheEnd to false and hasMorePages to true in my relation_scoped_model at initializeValues().
The only problem i'm still facing is that i'm getting duplicates, which happens because my screen is rebuilding twice.
When using scoped model, you should wrap the widgets that should respond to changes one by one with scoped model descendants. This way only those widgets can rebuild rather than the whole page rebuilding.
I was struct in a problem that i have to download pdf or images etc from network and store them in local storage.i am trying path provider plugin with GetApplicationDocumentDirectory its successfully storing files but not showing in device folder. how to create a directory and store files such as images pdfs etc that are visible to users. how could i achieve that.
thanks for help in advance
You can write to the device external storage as shown in the below example code by creating the folder
Hope it helps
class PDFDownloader extends StatefulWidget {
final String extension;
final String url;
final String fileName;
PDFDownloader(this.url, this.fileName,[this.extension='pdf']);
#override
_DownloadAppState createState() => new _DownloadAppState();
}
class _DownloadAppState extends State<PDFDownloader> {
bool downloading = false;
String _message;
var progressString = "";
Future<Directory> _externalDocumentsDirectory;
#override
void initState() {
//downloadFile();
checkPer();
// _bannerAd = createBannerAd()..load();
super.initState();
}
void checkPer() async {
await new Future.delayed(new Duration(seconds: 1));
bool checkResult = await SimplePermissions.checkPermission(
Permission.WriteExternalStorage);
if (!checkResult) {
var status = await SimplePermissions.requestPermission(
Permission.WriteExternalStorage);
//print("permission request result is " + resReq.toString());
if (status == PermissionStatus.authorized) {
await downloadFile();
}
} else {
await downloadFile();
}
}
#override
Widget build(BuildContext context) {
var scaffold= Scaffold(
appBar: AppBar(
title: Text("Download"),
),
body: Center(
child: downloading
? Container(
height: 120.0,
width: 200.0,
child: Card(
color: Colors.black,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
SizedBox(
height: 20.0,
),
Text(
"Downloading File: $progressString",
style: TextStyle(
color: Colors.white,
),
)
],
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_message ?? 'Please wait!!'),
SizedBox(
height: 10.0,
),
new RaisedButton(
textColor: Colors.white,
color: Colors.blueAccent,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0)),
onPressed: () {
Navigator.pop(context);
},
child: Text('Close'),
)
],
),
),
);
return WillPopScope(
onWillPop: _onWillPop,
child: scaffold,
);
}
Future<bool> _onWillPop() {
return new Future.value(!downloading);
}
Future<void> downloadFile() async {
var dio = new Dio();
var dir = await getExternalStorageDirectory();
var knockDir =
await new Directory('${dir.path}/iLearn').create(recursive: true);
print(widget.url);
await dio.download(widget.url, '${knockDir.path}/${widget.fileName}.${widget.extension}',
onProgress: (rec, total) {
//print("Rec: $rec , Total: $total");
if (mounted) {
setState(() {
downloading = true;
progressString = ((rec / total) * 100).toStringAsFixed(0) + "%";
});
}
});
if (mounted) {
setState(() {
downloading = false;
progressString = "Completed";
_message = "File is downloaded to your SD card 'iLearn' folder!";
});
}
print("Download completed");
}
}
To create a Directory of App in Internal Storage use this snippet:
Directory directory = await getExternalStorageDirectory();
String fileName =
"xyz.pdf";
String newPath = "";
print(directory);
List<String> paths = directory.path.split("/");
for (int x = 1; x < paths.length; x++) {
String folder = paths[x];
if (folder != "Android") {
newPath += "/" + folder;
} else {
break;
}
}
newPath = newPath + "/YourAppName";
directory = Directory(newPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
if (await directory.exists()) {
final File file = File(directory.path + "/$fileName");
// your logic for saving the file.
}
For a Detailed Explanation check out this Medium Article by Retroportal Studio:
https://retroportalstudio.medium.com/saving-files-to-application-folder-and-gallery-in-flutter-e9be2ebee92a
I'm trying to take video by using camera plugin and when I run project in Xcode to run real device since VS code doesn't recognize my phone.
The problem is when I switch camera front to rear like more than 5 less than 10 times even I call dispose(), app crash. Also when recording video and play recorded video, almost 50 ~ 60% crash.
Xcode says
Message from debugger: Terminated due to memory issue
I saw that flutter doesn't have any memory leaks and we don't need to care about memory management.
So, why is this happing?
Am I missing something or fluter error?
Does anyone know what's going on?
Code:
class CameraScreenState extends State<CameraScreen>
with TickerProviderStateMixin {
List<CameraDescription> _cameraDescriptions;
CameraController _cameraController;
VideoPlayerController _videoController;
AnimationController _animationController;
String imagePath;
String videoPath;
VoidCallback videoPlayerListener;
bool _isSelfie = false;
bool _isRecorded = false;
bool _isRecording = false;
bool _isReady = false;
#override
void initState() {
super.initState();
_setupCameras();
}
Future<void> _setupCameras() async {
try {
_cameraDescriptions = await availableCameras();
_cameraController = new CameraController(_cameraDescriptions[0], ResolutionPreset.high);
await _cameraController.initialize();
} on CameraException catch (_) {
// do something on error.
}
if (!mounted) return;
setState(() {
_isReady = true;
});
_animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 15),
);
}
#override
void deactivate() {
_videoController?.setVolume(0.0);
_videoController?.removeListener(videoPlayerListener);
super.deactivate();
}
#override
void dispose() {
_cameraController?.dispose();
_animationController?.dispose();
_videoController?.dispose();
super.dispose();
}
String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
Future _changeDirection(CameraDescription cameraDescription) async {
await _cameraController.dispose().then((_) async {
_cameraController =
new CameraController(cameraDescription, ResolutionPreset.high);
_cameraController.addListener(() {
if (mounted) setState(() {});
if (_cameraController.value.hasError) {
print(_cameraController.value.errorDescription);
}
});
try {
await _cameraController.initialize();
} on CameraException catch (e) {
print(e);
}
if (mounted) {
setState(() {});
}
});
}
_toSelfie() {
_changeDirection(_cameraDescriptions[1]);
}
_toForward() {
_changeDirection(_cameraDescriptions[0]);
}
void onVideoRecordButtonPressed() {
startVideoRecording().then((String filePath) {
if (mounted) setState(() {});
if (filePath != null) print('Saving video to $filePath');
});
}
void onStopButtonPressed() {
stopVideoRecording().then((_) {
if (mounted) setState(() {});
print('Video recorded to: $videoPath');
});
}
Future<String> startVideoRecording() async {
if (!_cameraController.value.isInitialized) {
print('Error: select a camera first.');
return null;
}
final Directory extDir = await getTemporaryDirectory();
final String dirPath = '${extDir.path}/Movies/flutter_test';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.mp4';
if (_cameraController.value.isRecordingVideo) {
// A recording is already started, do nothing.
return null;
}
try {
videoPath = filePath;
await _cameraController.startVideoRecording(filePath).then((_) async {
final animation = _animationController.forward(from: 0.0);
await animation.then((_) {
setState(() {
onStopButtonPressed();
_isRecorded = true;
});
});
});
} on CameraException catch (e) {
print(e);
return null;
}
return filePath;
}
Future<void> stopVideoRecording() async {
if (!_cameraController.value.isRecordingVideo) {
return null;
}
try {
await _cameraController.stopVideoRecording();
} on CameraException catch (e) {
print(e);
return null;
}
await _startVideoPlayer();
}
Future<void> _startVideoPlayer() async {
final VideoPlayerController vcontroller =
new VideoPlayerController.file(new File(videoPath));
videoPlayerListener = () {
if (_videoController != null && _videoController.value.size != null) {
// Refreshing the state to update video player with the correct ratio.
if (mounted) setState(() {});
_videoController.removeListener(videoPlayerListener);
}
};
vcontroller.addListener(videoPlayerListener);
await vcontroller.setLooping(true);
await vcontroller.initialize();
await _videoController?.dispose();
if (mounted) {
setState(() {
imagePath = null;
_videoController = vcontroller;
});
}
await vcontroller.play();
}
_startVideoRecordingCoountDown() {
onVideoRecordButtonPressed();
}
_stopVideoRecordingCountDown() {
setState(() {
_animationController.stop();
onStopButtonPressed();
_isRecorded = true;
});
}
#override
Widget build(BuildContext context) {
if (!_isReady) {
return new Container(
color: Colors.black,
child: new Center(child: new CircularProgressIndicator()));
}
return new Scaffold(
backgroundColor: Colors.black,
body: !_isRecorded
? new Stack(
children: <Widget>[
new AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: new CameraPreview(_cameraController),
),
!_isRecording
? new Align(
alignment: Alignment.topLeft,
child: new Container(
child: new FloatingActionButton(
heroTag: 'start',
backgroundColor: Colors.black.withOpacity(0.001),
elevation: 50.0,
child: new Center(
child: new Icon(Icons.clear, size: 28.0)),
onPressed: () {
Navigator.of(context).pop();
},
),
),
)
: new Container(),
new Align(
alignment: new Alignment(0.0, 1.0),
child: new Container(
margin: const EdgeInsets.only(bottom: 10.0),
child: new FloatingActionButton(
elevation: 30.0,
backgroundColor: Colors.white,
foregroundColor: Colors.amber,
child: _animationController.isAnimating
? new Countdown(
animation: new StepTween(
begin: 16,
end: 0,
).animate(_animationController),
)
: new Icon(Icons.play_arrow),
onPressed: () {
setState(() {
_isRecording = true;
});
!_animationController.isAnimating
? _startVideoRecordingCoountDown()
: _stopVideoRecordingCountDown();
},
),
),
),
!_isRecording
? new Align(
alignment: Alignment.bottomRight,
child: new Container(
margin:
const EdgeInsets.only(right: 15.0, bottom: 10.0),
child: new FloatingActionButton(
elevation: 50.0,
backgroundColor: Colors.black.withOpacity(0.001),
child: new Icon(Icons.cached, size: 35.0),
onPressed: () {
setState(() {
_isSelfie ? _toForward() : _toSelfie();
_isSelfie
? _isSelfie = false
: _isSelfie = true;
});
},
),
),
)
: new Container(),
],
fit: StackFit.expand,
)
: _videoController == null
? new Container(
color: Colors.black,
child: new Center(child: new CircularProgressIndicator()))
: _videoController.value.size != null
? new AspectRatio(
aspectRatio: _videoController.value.aspectRatio,
child: new VideoPlayer(_videoController),
)
: new Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: new VideoPlayer(_videoController)),
);
}
}
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]),
),
),
);
}
}