How to get the image path after selecting multiple images using pickMultiImage of image_picker in flutter - flutter

I'm trying to select multiple images so for this i used pickMultiImage method of image_picker.
Images are displaying on screen, but i need their path so that i can use it to upload on cloudinary.com.
here is my code
List<XFile>? _imageFileList3 = [];
Future pickMultipleImage() async {
if (_imageFileList3!.length == 4) {
showDialog(
context: context,
builder: (BuildContext context) {
return LoginSucessDailog(
text: 'You can\'t add more than 4 images.',
title: 'Warning.',
img: 'assets/img/alert.png');
});
} else {
try {
var image = await _picker.pickMultiImage();
//here i'll be using cloudinary code
setState(() {
_imageFileList3!.addAll(image!);
});
print(image);
print(_imageFileList3!.length);
setState(() {
isImageLoading = false;
});
} on CloudinaryException catch (e) {}
}
}
this is the part of code i'm using to upload images on Cloudinary using cloudinary_public package
CloudinaryResponse response = await cloudinary.uploadFile(
CloudinaryFile.fromFile(image!.path,
resourceType: CloudinaryResourceType.Image),
);
displaying images like this
addProductsImages() {
if (_imageFileList3!.length != 0) {
return SizedBox(
height: 80,
width: MediaQuery.of(context).size.width * 0.9,
child: GridView.builder(
shrinkWrap: true,
itemCount: _imageFileList3!.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.file(
File((_imageFileList3![index].path)),
width: MediaQuery.of(context).size.width * 0.35,
height: MediaQuery.of(context).size.height * 0.17,
fit: BoxFit.cover,
),
),
Align(
alignment: Alignment.topRight,
child: buildCancelIcon(Colors.white, () {
setState(() {
// _imageFileList!.removeAt(index);
});
}, Icons.cancel))
]));
}));
} else {
return Padding(
padding: const EdgeInsets.only(left: 70),
child:
Row(crossAxisAlignment: CrossAxisAlignment.center, children: []));
}
}
please help how to do this, or is there any way to select multiple images at once and upload them on cloudinary.

Please refer to below example code where user can pick maximum 5 images
Using these packages
images_picker: ^1.2.4
flutter_image_compress: ^0.7.0
class PickMultipleImagesScreen extends StatefulWidget {
const PickMultipleImagesScreen({Key key}) : super(key: key);
#override
_PickMultipleImagesScreenState createState() =>
_PickMultipleImagesScreenState();
}
class _PickMultipleImagesScreenState extends State<PickMultipleImagesScreen> {
final ValueNotifier<bool> attachMultipleImages = ValueNotifier<bool>(false);
List compressedPhotosList = ["place_holder"];
int maxImagesCount = 5;
pickPhotos() async {
List<Media> photosList = [];
photosList = await ImagesPicker.pick(
count: (compressedPhotosList != null &&
(compressedPhotosList.isNotEmpty) &&
(compressedPhotosList.length > 1))
? (maxImagesCount + 1 - compressedPhotosList.length)
: maxImagesCount,
pickType: PickType.all,
language: Language.System,
cropOpt: CropOption(
aspectRatio: CropAspectRatio(600, 400),
),
);
if (photosList != null && photosList.isNotEmpty && photosList.length > 0) {
for (int i = 0; i < photosList.length; i++) {
File photoCompressedFile =
await compressImage(File(photosList[i].path));
print("Images List: $photosList");
print("Path of UnCompressed File: ${photosList[i].path}");
compressedPhotosList.insert(
0,
photoCompressedFile.path.toString(),
);
print("Path of Compressed File: ${photoCompressedFile.path}");
print("Compressed Images List: $compressedPhotosList");
}
attachMultipleImages.value = !attachMultipleImages.value;
}
}
Future<File> compressImage(File file) async {
final filePath = file.absolute.path;
final lastIndex = filePath.lastIndexOf(new RegExp(r'.png|.jp'));
final splitted = filePath.substring(0, (lastIndex));
final outPath = "${splitted}_out${filePath.substring(lastIndex)}";
if (lastIndex == filePath.lastIndexOf(new RegExp(r'.png'))) {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
filePath, outPath,
minWidth: 1000,
minHeight: 1000,
quality: 50,
format: CompressFormat.png);
return compressedImage;
} else {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
filePath,
outPath,
minWidth: 1000,
minHeight: 1000,
quality: 50,
);
return compressedImage;
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: ValueListenableBuilder<bool>(
valueListenable: attachMultipleImages,
builder: (context, snapshot, child) {
return Scaffold(
body: (compressedPhotosList != null &&
compressedPhotosList.isNotEmpty &&
compressedPhotosList.length > 1)
? GridView.builder(
itemCount: (compressedPhotosList != null &&
compressedPhotosList.isNotEmpty &&
compressedPhotosList.length > 1 &&
(compressedPhotosList.length - 1 == maxImagesCount))
? compressedPhotosList.length - 1
: compressedPhotosList.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0),
itemBuilder: (BuildContext context, int index) {
return ((compressedPhotosList[index] == "place_holder") &&
compressedPhotosList.length - 1 != maxImagesCount)
? InkWell(
onTap: () async {
if (compressedPhotosList.length - 1 !=
maxImagesCount) {
pickPhotos();
}
},
child: Container(
margin: EdgeInsets.all(
5.0,
),
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
color: Colors.blueAccent,
child: Center(
child: Icon(
Icons.add,
size: ScreenUtil().setSp(24.0),
color: Colors.grey,
),
),
),
)
: Stack(
clipBehavior: Clip.none,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.file(
File(compressedPhotosList[index]),
fit: BoxFit.fitHeight,
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
filterQuality: FilterQuality.low,
errorBuilder: (context, error, stackTrace) {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().setHeight(105.0),
color: Colors.black,
);
},
),
),
Positioned(
bottom: 10,
right: 8,
child: InkWell(
onTap: () async {
compressedPhotosList.removeAt(index);
attachMultipleImages.value =
!attachMultipleImages.value;
},
child: CircleAvatar(
radius: 15.0,
backgroundColor: Colors.black45,
child: Icon(
Icons.delete_forever,
color: Colors.white,
size: 20,
),
),
),
)
],
);
},
)
: Center(
child: InkWell(
onTap: () {
pickPhotos();
},
child: Text("Attach Images"),
),
),
);
}
),
);
}
}

Related

type 'bool' is not a subtype of type 'List<ternakModel> in a type cast

I have problem in the emulator android. type 'bool' is not a subtype of type 'List in a type cast. I can't solve of this. I use provider ListBuilder. so I'm retrieving data using the provider in the form of a list. Can you help?
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
TernakProvider ternakProvider = Provider.of<TernakProvider>(context);
return Consumer<TernakProvider>(
builder: (context, providerData, _) => FutureBuilder(
future: providerData.getTernak(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text("Loading...");
}
List<ternakModel> ternak = snapshot.data as List<ternakModel>;
and this is file provider
class TernakProvider with ChangeNotifier {
List<ternakModel> _ternak = [];
List<ternakModel> get ternak => _ternak;
set ternak(List<ternakModel> ternak) {
_ternak = ternak;
notifyListeners();
}
Future<bool> getTernak() async {
try {
List<ternakModel> ternak = await TernakService().getTernak();
_ternak = ternak;
return true;
} catch (e) {
print(e);
return false;
}
}
}
and this is my service file
class TernakService {
String baseUrl = "BaseURL";
Future getTernak() async {
var url = Uri.parse("$baseUrl/api/ternak");
var response = await http.get(url);
print(response.body);
if (response.statusCode == 200) {
List data = jsonDecode(response.body)['data']['list'];
List<ternakModel> ternak = [];
for (var item in data) {
ternak.add(ternakModel.fromJson(item));
}
return ternak;
} else {
throw Exception('Gagal Get Ternak');
}
}
}
this is listview code
body: ListView.builder(
itemCount: ternak.length,
itemBuilder: (context, index) {
return Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DetailTernak(),
settings: RouteSettings(
arguments:
ternak[index].id_ternak.toString(),
),
),
);
},
// width: MediaQuery.of(context).size.width / 0.5,
// margin: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: MediaQuery.of(context)
.size
.width /
1.2,
margin: EdgeInsets.only(
left: 16,
right: 16,
top: 10,
bottom: 10),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(10),
border: Border.all(
color: const Color(0xffE5E5E5),
width: 1,
),
boxShadow: [
BoxShadow(
blurStyle: BlurStyle.outer,
color: Colors.black
.withOpacity(0.1),
spreadRadius: 0,
blurRadius: 8,
offset: const Offset(0,
0), // changes position of shadow
),
],
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: <Widget>[
Container(
margin: EdgeInsets.only(
left: 15, top: 15),
child: Row(
children: [
Text(
"ID ",
style: regular
.copyWith(
color: Color(
0xFF646464),
),
),
Text(
"${ternak[index].id_ternak}",
style: semibold,
)
],
),
),
]),
Change your getTernak inside TernakProvider to this:
Future<List<ternakModel>> getTernak() async {
try {
List<ternakModel> ternak = await TernakService().getTernak();
_ternak = ternak;
return ternak;
} catch (e) {
print(e);
return [];
}
}

Store Image in List<Xfile> from image urls

I have created a variable
List<Xfile> imageList;
using this variable I have showed the selected images in GridView.Builder and uploaded them.
But I want to store those uploaded images in this List to show them again in GridView.Builder.
Means How to store images from imageUrls in List
How can I achieve this?
Follow as follows:
Variables
final picker = ImagePicker();
File? file;
XFile? pickedImage;
bool isLoading = false;
List<File?> fileList = [];
Method to select image from gallery
Future pickImageFromGallery() async {
pickedImage = await picker.pickImage(source: ImageSource.gallery);
setState(() {
file = File(pickedImage!.path);
fileList.add(file);
});
}
And place in gridview as follows:
GridView.builder(
itemCount: fileList.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int i) {
return Container(
padding: const EdgeInsets.all(10),
child: Stack(
children: <Widget>[
SizedBox(
height: 100,
width: 100,
child: Image.file(File(fileList[i]!.path),fit: BoxFit.cover,),
),
Positioned(
right: 1,
child: GestureDetector(
onTap: () {
setState(() {
dltImages(fileList[i]);
});
},
child: const Icon(Icons.cancel, color: Colors.red),
))
],
),
);
},
),
Find full code at:
https://github.com/nbnD/image_picker_flutter/blob/master/lib/homepage.dart
I do like this if there is multi images upload
class PickImagesPage extends StatefulWidget {
const PickImagesPage({super.key, required this.initialUrls});
final List<String> initialUrls;
#override
State<PickImagesPage> createState() => _PickImagesPageState();
}
class _PickImagesPageState extends State<PickImagesPage> {
#override
void initState() {
urls = widget.initialUrls;
super.initState();
}
List<String> urls = [];
List<File> files = [];
List<String> removedUrls = [];
final Repo repo = Repo();
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme;
final scheme = theme.colorScheme;
return LoadingLayer(
child: Scaffold(
bottomNavigationBar: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: ElevatedButton(
onPressed:
files.isNotEmpty || widget.initialUrls.length != urls.length
? () async {
try {
await repo.uploadImages(
files: files,
urls: urls,
removedUrls: removedUrls,
);
Navigator.pop(context);
} catch (e) {
AppSnackbar(context).error(e);
if (kDebugMode) {
print(e);
}
}
}
: null,
child: const Text(Labels.save),
),
),
appBar: AppBar(
title: const Text(
Labels.ambienceImages,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final List<XFile> pickedFiles = await pickImages();
if (pickedFiles.isNotEmpty) {
setState(() {
files.addAll(pickedFiles.map((e) => File(e.path)));
});
}
},
child: const Icon(Icons.add),
),
body: GridView.count(
padding: const EdgeInsets.all(12),
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
children: [
...urls
.map(
(e) => GestureDetector(
onTap: () {
setState(() {
urls.remove(e);
removedUrls.add(e);
});
},
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: scheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: NetworkImage(e),
),
),
),
),
)
.toList(),
...files
.map(
(e) => Container(
clipBehavior: Clip.antiAlias,
alignment: Alignment.topRight,
decoration: BoxDecoration(
color: scheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: FileImage(e),
),
),
child: SizedBox(
height: 40,
width: 40,
child: RawMaterialButton(
elevation: 0,
focusElevation: 0,
hoverElevation: 0,
shape: const CircleBorder(),
fillColor: theme.cardColor.withOpacity(0.5),
onPressed: () {
setState(() {
files.remove(e);
});
},
child: const Icon(Icons.remove),
),
),
),
)
.toList(),
GestureDetector(
onTap: () async {
final List<XFile> pickedFiles = await pickImages();
if (pickedFiles.isNotEmpty) {
setState(() {
files.addAll(pickedFiles.map((e) => File(e.path)));
});
}
},
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: scheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(20),
),
child: Stack(
children: const [
Center(
child: Icon(Icons.add),
),
PickImageLabel(),
],
),
),
),
],
),
),
);
}
}
class Repo {
Future<void> uploadImages(
{required List<String> urls,
required List<File> files,
required List<String> removedUrls}) async {
List<String> newUrls = [];
for (var file in files) {
final url = await upload(file);
newUrls.add(url);
}
for (var url in removedUrls) {
await deleteImage(url);
}
await saveImages(urls + newUrls);
}
}

How to show images upon app start in flutter?

My app, when opened, doesn't show images stored on the Hive DB. After I upload a new one, it shows up and keeps showing fine.
If the app gets close, it won't show it again until it gets uploaded again
I tried to use listenable, and setStates(), and nothing worked, or I implemented it wrong.
import "dart:io";
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:hive_flutter/adapters.dart";
import "package:image_picker/image_picker.dart";
import "package:vortexcdl/model/incident_model.dart";
import "package:vortexcdl/widget/constants.dart";
import "../boxes.dart";
late Box boxImg;
class ImagesPage extends StatefulWidget {
ImagesPage({Key? key}) : super(key: key);
State<ImagesPage> createState() => _ImagesPageState();
}
class _ImagesPageState extends State<ImagesPage> {
//Variables
List? allImages = [];
List? pickedImages;
ImagesToSend imageObj = ImagesToSend();
///Get selection o fimages from Adnroid gallery
getImagesFromHive() async {
if (boxImg.containsKey("images_box")) {
// print(" BOX FOUND");
imageObj.images = await boxImg.getAt(0).images;
} else {
// print("Box does not exist...");
// imageObj.images = []; //Prevents null error;
}
}
Future pickImage(source) async {
final pickedImages = await source;
// print(pickedImages.length);
// print("%%%%%%%%%%%%%%%%");
if (pickedImages == null) return;
if (pickedImages is XFile) {
allImages!.add(pickedImages);
// print("############ Yes");
} else {
allImages!.addAll(pickedImages);
}
getImagesFromHive();
for (var i in allImages!)
imageObj.images.add(File(i.path).readAsBytesSync());
allImages = []; //Prevents deleted images to come back
boxImg.put("0", imageObj); //Save images in Hive
setState(() {});
}
//################### BUILD ##############################
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Images of the accident")),
body: _mainWidget(),
floatingActionButton: SpeedDial(
icon: Icons.collections,
backgroundColor: mainColor(),
children: [
SpeedDialChild(
child: Icon(
Icons.photo,
color: Colors.white,
),
backgroundColor: mainColor(),
label: "Gallery",
onTap: () async {
pickImage(ImagePicker().pickMultiImage());
},
),
SpeedDialChild(
child: Icon(
Icons.camera,
color: Colors.white,
),
backgroundColor: mainColor(),
label: "Camera",
onTap: () async {
pickImage(ImagePicker().pickImage(source: ImageSource.camera));
}),
],
),
);
}
Widget _mainWidget() {
return Column(children: [
ElevatedButton(
onPressed: () {
boxImg.clear();
setState(() {});
},
child: Text("Erase DB")),
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemCount: imageObj.images.length,
itemBuilder: (BuildContext context, int index) {
return generateGallery(index);
}))
]);
}
Widget generateGallery(index) {
getImagesFromHive();
return listenImages(Stack(
fit: StackFit.loose,
alignment: AlignmentDirectional.topEnd,
children: [
RawMaterialButton(
onPressed: () {},
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Image.memory(
imageObj.images[index],
width: 200,
height: 200,
fit: BoxFit.cover,
// child: Image.memory(
// boxImg.getAt(0).images[index],
// width: 200,
// height: 200,
// fit: BoxFit.cover,
))),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 10, 0),
child: ClipOval(
child: Container(
height: 40,
width: 40,
color: Colors.black.withAlpha(100),
child: IconButton(
onPressed: () {
setState(() {
imageObj.images.removeAt(index);
boxImg.put("0", imageObj);
});
},
icon: Icon(
Icons.close,
size: 20,
color: Colors.white,
)))))
]));
}
Widget listenImages(Widget w) {
return ValueListenableBuilder<Box<ImagesToSend>>(
valueListenable: BoxesImg.getImages().listenable(),
builder: (context, box, _) {
// print("####### LISTENING TO BOX IMAGES");
// setState(() {});
return w;
},
);
}
}
I tried multiple modifications and I was not able to solve this

Flutter FutureBuilder calling function continuously

I have simple function which is calling data from firestore and filtering data. But issue is my futurebuilder keeps on loader situation (Data is called successfully i can see in console but now showing in future) I think its because my fucntion is calling in loop or something i have try to print something in my function which indicates me that my function is not stopping and thats why i think my futureBuilder keeps on loading.
My code
Future<List> getCustomerList() async {
print('calling');
String uUid1 = await storage.read(key: "uUid");
String uName1 = await storage.read(key: "uName");
String uNumber1 = await storage.read(key: "uNumber");
setState(() {
uUid = uUid1;
uName = uName1;
uNumber = uNumber1;
});
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Customers');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs
.where((element) => element['sellerUID'] == uUid)
.map((doc) => doc.data())
.toList();
double gGive = 0;
double gTake = 0;
double gCal = 0;
for (int i = 0; i < allData.length; i++) {
// print(allData[i]);
// print('give ${double.parse(allData[i]['give'].toString()) }');
// print('take ${double.parse(allData[i]['take'].toString()) }');
double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString()) >
0
? gGive += double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString())
: gTake += double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString());
}
// print(gGive);
// print(gTake);
setState(() {
Gtake = gGive.toString().replaceAll("-", "");
Ggive = gTake.toString().replaceAll("-", "");
});
if (greenBox) {
var check = allData.where((i) => i['take'] > i['give']).toList();
return check;
} else if (redBox) {
var check = allData.where((i) => i['give'] > 1).toList();
return check;
} else {
return allData;
}
}
And my futureBuilder look like this
Expanded(
child: Container(
height: Height * 0.5,
child: FutureBuilder(
future: getCustomerList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
list = snapshot.data;
return SingleChildScrollView(
child: Column(
children: [
Container(
height: Height * 0.5,
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder:
(BuildContext context,
int index) {
var showThis = list[index]
['give'] -
list[index]['take'];
return list[index]
['customerName']
.toString()
.contains(searchString)
? GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CustomerData(
data: list[
index])),
);
},
child: Padding(
padding:
const EdgeInsets
.only(
left: 13,
right: 13),
child: Container(
decoration:
BoxDecoration(
border: Border(
top: BorderSide(
color: Colors
.grey,
width:
.5)),
),
child: Padding(
padding:
const EdgeInsets
.all(
13.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
child:
Text(
list[index]['customerName'][0]
.toString(),
style:
TextStyle(fontFamily: 'PoppinsBold'),
),
backgroundColor:
Color(0xffF7F9F9),
),
SizedBox(
width:
20,
),
Text(
list[index]['customerName']
.toString(),
style: TextStyle(
fontFamily:
'PoppinsMedium'),
),
],
),
Text(
'RS ${showThis.toString().replaceAll("-", "")}',
style: TextStyle(
fontFamily:
'PoppinsMedium',
color: list[index]['give'] - list[index]['take'] <
0
? Colors.green
: Colors.red),
),
],
),
),
),
),
)
: Container();
},
),
)
],
),
);
} else
return Center(
heightFactor: 1,
widthFactor: 1,
child: SizedBox(
height: 70,
width: 70,
child: CircularProgressIndicator(
strokeWidth: 2.5,
),
),
);
}),
),
),
I am damn sure its because futurebuilder keeps calling function which is returning data but because of keeps calling functions my Futurebuilder keeps showing loading.
You should not call setState inside the future that you are giving to the FutureBuilder.
The state actualization will cause the FutureBuilder to re-build. Meaning triggering the future again, and ... infinite loop !

Flutter ListView not updating on data change

I am new in Flutter. Currently learning and developing a flutter project. Here is my code. But my list view is not updating. Advance thanks for pointing out any mistake
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:stacked/stacked.dart';
import 'package:sx_tvapp_app/data/network/models/favourite_item.dart';
import 'package:sx_tvapp_app/ui/views/favourite_items/favourite_items_viewmodel.dart';
class FavouriteItemsView extends StatefulWidget {
final FavouriteItemsViewType type = FavouriteItemsViewType.one;
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _FavouriteItemsViewState();
}
}
class _FavouriteItemsViewState extends State<FavouriteItemsView> {
final String title = 'お気に入り';
List<String> items = ['A', 'B', 'C', 'D', 'E', 'F'];
FavouriteItemPage page;
List<FavouriteItemContent> contents = List();
bool isLoading = false;
FavouriteItemsViewModel viewModel;
void showLoading() {
setState(() {
isLoading = true;
});
}
Future loadData() async {
print('loadData');
print(contents.length.toString());
if (page == null) {
this.viewModel.getFavouriteItemForLoggedOutUser();
showLoading();
} else {
if (page.totalPages > page.number) {
this.viewModel.getFavouriteItemForLoggedOutUser(page: page.number + 1);
showLoading();
}
}
}
void bindModel(FavouriteItemsViewModel viewModel) {
viewModel.pageSubject.listen((value) {
print(value);
page = value;
});
viewModel.favouriteItemSubject.listen((value) {
print(' content is going to be added');
print(value.contents.length);
setState(() {
// contents.addAll(value.contents);
for (int i = 0; i < value.contents.length; i++) {
var commonItem = contents.where((element) {
return element.id == value.contents[i].id;
}).toList();
if (commonItem.length == 0) {
print('item is being addedf');
contents.add(value.contents[i]);
}
}
// contents = contents.toSet().toList();
isLoading = false;
});
});
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return ViewModelBuilder<FavouriteItemsViewModel>.reactive(
viewModelBuilder: () => GetIt.instance.get<FavouriteItemsViewModel>(),
onModelReady: (model) {
this.viewModel = model;
this.bindModel(model);
// this.loadData();
model.getFavouriteItemForLoggedOutUser();
},
builder: (context, viewModel, child) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: <Widget>[
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (!isLoading &&
scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent) {
loadData();
}
},
child: buildListView(),
),
),
Container(
height: isLoading ? 50.0 : 0,
color: Colors.transparent,
child: Center(
child: new CircularProgressIndicator(),
),
),
],
),
);
},
);
}
Widget buildListView() {
return ListView.builder(
itemCount: contents.length,
padding: EdgeInsets.fromLTRB(0, 9, 0, 9),
itemBuilder: (context, index) {
return buildRow(contents[index]);
});
}
Widget buildRow(FavouriteItemContent content) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
width: 15,
height: 0,
),
//TODO: Handle this after null image url issue is fixed
// CachedNetworkImage(
// fit: BoxFit.fill,
// height: 25,
// width: 25,
// imageUrl: content.channelIconUrl,
// placeholder: (context, url) => CircularProgressIndicator(),
// errorWidget: (context, url, error) => new Icon(Icons.error),
// ),
getImage(content.channelIconUrl, 25, 25),
Container(
width: 9,
height: 0,
),
Container(
child: Text(
content.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
letterSpacing: -0.25,
color: Color.fromRGBO(96, 96, 96, 1.0),
),
),
),
],
),
Container(
width: 0,
height: 30,
),
//TODO: Handle this after null image url issue is fixed
// CachedNetworkImage(
// fit: BoxFit.fill,
// height: 211,
// width: double.infinity,
// imageUrl: content.imageUrl,
// placeholder: (context, url) => CircularProgressIndicator(),
// errorWidget: (context, url, error) => new Icon(Icons.error),
// ),
getImage(content.imageUrl, double.infinity, 211),
Container(
width: 0,
height: 13,
),
Padding(
padding: EdgeInsets.fromLTRB(15, 12.5, 15, 9),
child: Text(content.details),
)
],
);
}
Widget getImage(String url, double width, double height) {
if (url != null) {
return CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Image(
image: AssetImage(
'assets/images/product_detail/product_detail_placeholder.png'),
),
);
} else {
return Image(
width: width,
height: height,
image: AssetImage(
'assets/images/product_detail/product_detail_placeholder.png'),
);
}
}
}
enum FavouriteItemsViewType { one, two, three, four }
The thing I do here is, I request for a get API at the beginning which gives me data for the first page. Then I request again after scrolling down to the bottom. This is a ListView with pagination.
What you would likely want to use is a StreamBuilder which will rebuild whatever it contains when new data arrives from the stream. Streambuilder will become your go to for most data in flutter.
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html