Related
I am using Flutter + the camera package to create a photo and show it in a preview.
Inside the preview the user can decide if he wants to keep / use this photo - or if he wants to repeat that shot.
Problem:
When repeating the photo, the Preview-Page shows the same image as in the first try.
When using the image (sending it to an API) the user will be redirected to the page that initiated the camera call. But even there - when doing it again, the old preview is visible.
Initiating Page (Let's call it "StartPage")
SizedBox(
width: double.infinity,
child: IconButton(
icon: const Icon(Icons.camera_alt_outlined),
iconSize: 80,
color: Colors.grey,
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoPage()))
.then((value) => setState(() {}));
},
),
)
The PhotoPage shows the live camera image (Everything works fine and API call works)
class PhotoPage extends StatefulWidget {
#override
_PhotoPageState createState() => _PhotoPageState();
}
class _PhotoPageState extends State<PhotoPage> {
CameraController? cameraController;
List? cameras;
int? selectedCameraIndex;
String? imgPath;
Future initCamera(CameraDescription cameraDescription) async {
if (cameraController != null) {
await cameraController!.dispose();
}
cameraController =
CameraController(cameraDescription, ResolutionPreset.veryHigh);
cameraController!.addListener(() {
if (mounted) {
setState(() {});
}
});
if (cameraController!.value.hasError) {
print('Camera Error ${cameraController!.value.errorDescription}');
}
try {
await cameraController!.initialize();
} catch (e) {
showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
Widget cameraPreview() {
if (cameraController == null || !cameraController!.value.isInitialized) {
return Text(
'Loading',
style: TextStyle(
color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold),
);
}
return AspectRatio(
aspectRatio: cameraController!.value.aspectRatio,
child: CameraPreview(cameraController!),
);
}
Widget cameraControl(context) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.centerRight,
child: FloatingActionButton.extended(
icon: Icon(Icons.camera),
label: Text(''),
backgroundColor: Colors.green,
onPressed: () {
onCapture(context);
}))
],
//),
);
}
onCapture(context) async {
try {
var p = await getTemporaryDirectory();
var name = 'test';
var path = "${p.path}/$name.png";
XFile image = await cameraController!.takePicture();
image.saveTo(path);
await cameraController!.takePicture().then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PreviewScreen(
imgPath: path,
fileName: "$name.png",
key: UniqueKey(),
))).then((value) => setState(() {}));
});
} catch (e) {
showCameraException(e);
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
]);
availableCameras().then((value) {
cameras = value;
if (cameras!.length > 0) {
setState(() {
selectedCameraIndex = 0;
});
initCamera(cameras![selectedCameraIndex!]).then((value) {});
} else {
print('No camera available');
}
}).catchError((e) {
print('Error : ${e.code}');
});
}
#override
dispose() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
]);
imgPath = '';
cameraController!.dispose();
PaintingBinding.instance!.imageCache!.clear();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(children: <Widget>[
CameraPreview(cameraController!),
Align(
alignment: Alignment.bottomCenter,
child: Image(
image: new AssetImage(
"assets/layer.png",
),
)),
Align(
alignment: Alignment.bottomCenter,
child: Text(
"Please hold the phone in Landscape mode",
textAlign: TextAlign.center,
textScaleFactor: 1.3,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
).tr(),
),
cameraControl(context),
]),
);
}
showCameraException(e) {
String errorText = 'Error ${e.code} \nError message: ${e.description}';
}
}
So I am pushing the path of the Image to the PreviewScreen:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PreviewScreen(
imgPath: path,
fileName: "$name.png",
key: UniqueKey(),
))).then((value) => setState(() {}));
});
My PreviewScreen looks as follows:
class PreviewScreen extends StatefulWidget {
String? imgPath;
String? fileName;
PreviewScreen(
{required this.imgPath, required this.fileName, required Key key})
: super(key: key);
#override
_PreviewScreenState createState() => _PreviewScreenState();
}
final SaveController controller = Get.put(SaveController());
class _PreviewScreenState extends State<PreviewScreen> {
/* #override
void initState() {
super.initState();
} */
#override
void dispose() {
SaveController().dispose();
_PreviewScreenState().dispose();
PaintingBinding.instance!.imageCache!.clear();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
//child: Stack(
//crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(children: [
Expanded(
flex: 2,
child: Image.file(
File(widget.imgPath!),
fit: BoxFit.cover,
),
)
]),
Align(
alignment: Alignment.bottomLeft,
child: FloatingActionButton.extended(
icon: Icon(Icons.repeat_outlined),
label: Text('Repeat').tr(),
backgroundColor: Colors.red,
onPressed: () {
widget.imgPath = '';
setState(() {});
Navigator.pop(context);
}
//Get.back();
),
),
Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton.extended(
icon: Icon(Icons.check_circle_outline_sharp),
label: Text('Confirm').tr(),
backgroundColor: Colors.green,
onPressed: () {
SystemServices.savePhoto(widget.imgPath!)
.then((value) => setState(() {}));
},
),
),
],
),
);
}
Future getBytes() async {
Uint8List bytes = File(widget.imgPath!).readAsBytesSync();
// print(ByteData.view(buffer))
return ByteData.view(bytes.buffer);
}
}
The "Repeat"-Function looks as follows:
onPressed: () {
widget.imgPath = '';
setState(() {});
Navigator.pop(context);
}
Unfortunately, I really have no clue anymore.
As far as I can see it (I am a beginner in Flutter), the state is cleared and variables are empty.
Can someone tell me, why the photo in the PreviewScreen remains the same? What am I doing wrong?
Thank you very much, I really appreciate any kind of tip.
It seems like, this is the solution to for that problem:
#override
void initState() {
imageCache!.clear();
imageCache!.clearLiveImages();
super.initState();
}
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 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 just starting with Flutter, finished the first codelab and tried to add some simple functionality to it.
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
theme: new ThemeData(primaryColor: Colors.deepOrange),
home: new RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
createState() => new RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = new TextStyle(fontSize: 18.0);
final _saved = new Set<WordPair>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Startup Name Generator'),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.list),
onPressed: _pushSaved,
)
],
),
body: _buildSuggestions(),
);
}
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return new Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new IconButton(
icon: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border),
color: alreadySaved ? Colors.red : null,
onPressed: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
));
}
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
final tiles = _saved.map(
(pair) {
return _buildRow(pair);
// new ListTile(
// title: new Text(
// pair.asPascalCase,
// style: _biggerFont,
// ),
// );
},
);
final divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return new Scaffold(
appBar: new AppBar(
title: new Text('Saved Suggestions'),
),
body: new ListView(children: divided),
);
},
),
);
}
}
In the Save suggestions screen I built the same row as in the Sugestions Screen.
In the Saved Sugstions screen when you click the heart icon the element is removed from the array of saved items but the screen is not re-rendered.
what am I doing wrong here?
thanks!
Update
Actually your app is working perfectly fine with me :/
Because you are not communicating the state change with the icon change. You are already changing state based on alreadySaved, notice how you managed setState()
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
In the previous block you are only removing or adding to your favourite list based on the boolean value of alreadySaved and you are not telling setState to change anything else. That is why the following does not produce a re-render even though alreadySaved is switching values
///These two lines do not know what is happening
icon: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border),
color: alreadySaved ? Colors.red : null,
So you can instead do the following
icon: new Icon(_whichIcon), //initialized var _whichIcon = Icons.favorite_border
color: _whichIconColor, //Initialized var _whichIconColor = Colors.transparent
And your setState would be:
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_whichIcon = Icons.favorite_border ;
_whichIconColor = Colors.transparent;
} else {
_saved.add(pair);
_whichIcon = Icons.favorite ;
_whichIconColor = Colors.red;
}
});
Or simpler you can do it like this, and keep your icon logic unchanged:
bool alreadySaved = false;
...
setState(() {
if (_saved.contains(pair)) {
_saved.remove(pair);
alreadySaved = false;
} else {
_saved.add(pair);
alreadySaved = true;
}
});
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
);