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
Related
I am facing an issue when I pull to refresh, I need that after pull to refresh gesture new articles available will be visible in the news page, but this doesn't happen.
I created the cache system, but once I pull to refresh new data, the news page doesn't load new articles.
What can I do to solve this issue?
News page
class _NewsPageState extends State<NewsPage> {
/// News service
final ApiNewsPage categoryNews = ApiNewsPage();
late bool isLoading;
/// Start Refresh indicator upload new articles function
final NewArticlesPage updateArticles = NewArticlesPage();
ScrollController scrollController = ScrollController();
//final List<ArticleModel> _posts = [];
//
#override
void initState() {
super.initState();
refreshArticles();
}
/// Call this when the user pull down the screen
Future<void> refreshArticles() async {
Future.delayed(const Duration(seconds: 2), () {
setState(() {
updateArticles.updateArticles();
print("Uploading new articles");
//_posts.add();
});
return updateArticles.updateArticles();
});
}
/// End Refresh indicator upload new articles function
///
///
#override
Widget build(BuildContext context) {
return RefreshIndicator(
displacement: 0,
color: assofacileMainColor,
backgroundColor: Colors.white,
onRefresh: refreshArticles,
child: Container(
child: FutureBuilder<List?>(
future: categoryNews.getNewsArticles(),
builder: (context, snapshot) { // AsyncSnapshot
if (snapshot.hasData) {
if (snapshot.data?.length == 0) {
return const NoArticle();
}
return ListView.builder(
controller: scrollController,
itemCount: snapshot.data?.length++,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, i) {
return Card(
margin: const EdgeInsets.all(8),
elevation: 5,
shadowColor: Colors.black26,
color: Colors.white,
child: InkWell(
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 190,
width: double.infinity,
child:
Image(
image: AdvancedNetworkImage(
snapshot.data![i]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["medium"]["source_url"],
useDiskCache: true,
cacheRule: const CacheRule(maxAge: Duration(days: 1)),
),
fit: BoxFit.cover,
),
// CachedNetworkImage(
// cacheManager: CacheManager(
// Config(snapshot.data![i]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["medium"]["source_url"],
// stalePeriod: const Duration(days: 1),
// )
// ),
// //cacheManager: DioCacheManager.instance,
// imageUrl: snapshot.data![i]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["medium"]["source_url"],
// // checkIfUrlContainsPrefixHttps(
// // _post != null
// // ? _post[0].urlImageSource
// // : "https:" + snapshot.data![i]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["thumbnail"]["source_url"],
// // ),
// //snapshot.data![i]["_embedded"]["wp:featuredmedia"][0]["link"],
// //"https:" + snapshot.data![i]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["medium"]["source_url"],
// fit: BoxFit.cover,
// placeholder: (context, url) => Image.asset("assets/gif/shimmer.gif",
// width: double.infinity,
// height: 190,
// fit: BoxFit.cover,
// ),
// errorWidget: (context, url, error) => Image.asset("assets/images/unloadedImage.png",
// width: 250, height: 250),
// ),
),
// Title article
Column(
children: [
Padding(
padding: const EdgeInsets.only(left:16, top: 16, bottom: 16),
child: Row(
children: [
Expanded(
child: Text(
snapshot.data![i]["title"]["rendered"]
.replaceAll("’", "'")
.replaceAll("<p>", "")
.replaceAll("</p>", ""),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: "Raleway",
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
//softWrap: false,
),
),
],
),
)
],
),
],
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(data: snapshot.data?[i]),
),
);
},
),
);
},
);
} else if (snapshot.hasError) {
return const NoInternet();
} else {
/// Shimmer
return ShimmerEffect();
}
}
),
),
);
}
}
Fetch and create cache
class ApiNewsPage {
final String url = newsJsonLink;
Future<List?> getNewsArticles() async {
String nameDB = "articledata.json";
var dir = await getTemporaryDirectory();
File file = File(dir.path + "/" + nameDB);
if(file.existsSync()) {
print("Loading Articles from cache");
var jsonDB = file.readAsStringSync();
List response = json.decode(jsonDB);
return response;
} else {
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
//await File(nameDB).exists();
var jsonResponse = response.body;
List newArticles = json.decode(jsonResponse);
//File(nameDB).deleteSync(recursive: true);
/// save json in local file
file.writeAsStringSync(jsonResponse, flush: true, mode: FileMode.write);
print("Fetching new articles from internet");
return newArticles;
}
}
}
}
// Create new db
class NewArticlesPage {
final String url = newsJsonLink;
Future<List?> updateArticles() async {
try {
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
print("Fetching new articles from internet");
return jsonDecode(response.body);
} else {
return Future.error("Impossibile ricevere i dati, prova a controllare la connessione");
}// ignore: non_constant_identifier_names
} catch (SocketException) {
return Future.error("Impossibile caricare gli articoli");
}
}
}
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 [];
}
}
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
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"),
),
),
);
}
),
);
}
}
When I'm trying to remove item(like 0 index item) from this listview using provider it removed the last item from the list. while I'm deleting last element from the list successfully remove last item. I'm bit confusing why this kind of problem is happening to me.
Here I'm posting some code please check give your valuable suggestion. Also demonstrate on this video what issue is happening
Link:https://drive.google.com/file/d/1UYl8Z7vEj_tZCaYzqe0VqZL2iMla5nIZ/view?usp=sharing
Expected result: Whenever user press delete button then delete that particular row(item).
Delete method:- This is the delete method It'll be call when user press delete button from the list.
Future<void> acceptdeclinerequest(String requestStatus,int requestId) async{
String token = await CustomPreferences.getpreferences('token');
Map<String, String> requestHeaders;
if (token.isNotEmpty) {
requestHeaders = {
'Accept': 'application/json',
'Authorization': 'Bearer ' + token
};
} else {
requestHeaders = {
'Accept': 'application/json',
};
}
var reqdata = {
"request_id":requestId.toString(),
"status":requestStatus
};
print('accept request data is $reqdata');
try
{
final response =
await http.post(Connection.url + 'respond-place-request', headers: requestHeaders,body: reqdata);
if (response.statusCode == 200) {
Map<String, dynamic> responseJson = json.decode(response.body);
final existingProductIndex = _items.indexWhere((prod) => prod.id == requestId);
var existingProduct = _items[existingProductIndex];
_items.removeAt(existingProductIndex);
notifyListeners();
return responseJson;
} /*else if (response.statusCode == 500) {
return servererrorresponse;
}*/
} catch (exception) {
throw exception;
}
}
Main Widget class: This is the main widget class where I define Listview widget. I've used provider to get data from api which is written in modal class to populate in Listview and Listview child widgets is in seprate class which is RequestWidgets. In this class I've passed rowitems data to show in listview.
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
var connectionstatus;
var product;
var _isInit = true;
var _isLoading = false;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<BandRequestModal>(context).getBandRequestList().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
connectionstatus = Provider.of<ConnectivityResult>(context);
product = Provider.of<BandRequestModal>(context, listen: false);
// getRequestData();
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
key: _scaffoldKey,
appBar: CustomAppbar(
_scaffoldKey, Constants.requests, 100.0, filterRecord),
endDrawer: MenuDrawer(),
body:
/*(connectionstatus == ConnectivityResult.wifi ||
connectionstatus == ConnectivityResult.mobile)
? */
Consumer<BandRequestModal>(builder: (context, modal, child) {
return !_isLoading
? Container(child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints constraints) {
return Container(
height: constraints.maxHeight,
child: modal.item.length > 0
? ListView.builder(
padding:
EdgeInsets.only(top: 10.0, bottom: 0.0),
itemCount: modal.item.length,
shrinkWrap: true,
// physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, int i) {
return RequestWidgets(data: modal.item[i]);
})
: Center(
child: new Text(
Constants.norecordfound,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold),
),
),
// ],
// ),
);
}))
: Comman.loadingIndicator(Theme.of(context).primaryColor);
})
// : Comman.nointernetconnection(context)
// FutureBuilder<BandRequestModal>(
// future: Connection.bandRequestList(),
// builder: (context, snapshot) {
// switch (snapshot.connectionState)
// {
// case ConnectionState.none:
// break;
// case ConnectionState.waiting:
// return Comman.loadingIndicator(
// Theme.of(context).primaryColor);
// break;
// case ConnectionState.active:
// break;
// case ConnectionState.done:
// if (snapshot.hasError) {
// return Center(
// child: new Text(Constants.servererror),
// );
// }else if(snapshot.data==null){
// return Center(
// child: new Text(Constants.servererror),
// );
// } else if (snapshot.data.data.length == 0) {
// return Center(
// child: new Text(
// Constants.norecordfound,
// style: TextStyle(
// fontSize: 20.0, fontWeight: FontWeight.bold),
// ),
// );
// } else {
// return ListView.builder(
// padding:
// EdgeInsets.only(top: 10.0, bottom: 60.0),
// itemCount: snapshot.data.data.length,
// shrinkWrap: true,
// physics: NeverScrollableScrollPhysics(),
// itemBuilder: (context, int i) {
// return RequestWidgets(data:snapshot.data.data[i]);
// });
// }
// break;
// }
// }):Comman.nointernetconnection(context)
));
}
Child widget class: This is the row items class of listview In this class we used many widgets to show place data.
class _RequestWidgetsState extends State<RequestWidgets> {
var getData;
var product;
#override
void initState() {
// TODO: implement initState
getData = widget.data;
super.initState();
}
#override
Widget build(BuildContext context) {
product = Provider.of<BandRequestModal>(context, listen: false);
return Container(
// alignment: Alignment.topLeft,
margin: EdgeInsets.only(top: 5.0),
child: ListTile(
// contentPadding: EdgeInsets.zero,
key: ObjectKey(getData),
leading: CircleAvatar(
radius: 30,
backgroundColor: Colors.transparent,
child: ClipOval(
child: (getData.placeDetails.image != null &&
getData.placeDetails.image != '')
? Image.network(
getData.placeDetails.image,
width: 90,
height: 90,
fit: BoxFit.cover,
)
: Image.asset(
Res.defaultImage,
width: 90,
height: 90,
fit: BoxFit.cover,
)),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(getData.placeDetails.name,
style: TextStyle(
fontSize: 16.0,
fontFamily: 'Metropolis',
color: CustomColors.commentTitleColor))),
],
),
subtitle: Container(
margin: EdgeInsets.only(top: 1.0),
child: Column(children: <Widget>[
Container(
margin: EdgeInsets.only(top: 1.0),
child: Row(children: <Widget>[
Expanded(
child: Text(getData.placeDetails.address,
style: TextStyle(
fontSize: 15.0,
height: 1.2,
fontFamily: 'Metropolis',
color: CustomColors.commentSubtitleColor))),
]),
),
Container(
margin: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[],
)),
Divider(
color: CustomColors.commentlineColor,
thickness: 0.8,
)
])),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
child: CircleAvatar(
radius: 20,
backgroundColor: Colors.green,
child: Icon(
Icons.check,
color: Colors.white,
),
),
onTap: () {
acceptrejectpopup('1');
// {
// print('accept data $data');
// Comman.hideLoading(context);
// Comman.showSnakBar(data['message'],context);
// });
},
),
SizedBox(
width: 15.0,
),
GestureDetector(
child: CircleAvatar(
backgroundColor: Colors.red,
child: Icon(
Icons.clear,
color: Colors.white,
),
),
onTap: () {
// Comman.showLoading(context);
acceptrejectpopup('0');
/*product.acceptdeclinerequest('0',getData.id.toString()).then((data){
print('decline data $data');
Comman.hideLoading(context);
Comman.showSnakBar(data['message'],context);
});*/
},
)
],
),
),
);
}
//accept and reject
void acceptRejectRequest(String requestStatus) async {
try {
var response =
await product.acceptdeclinerequest(requestStatus, getData.id);
if (response['status'] == Constants.status_true) {
Comman.hideLoading(context);
Comman.showSnakBar(response['message'], context);
// setState(() {});
} else {
Comman.hideLoading(context);
}
} catch (exception) {
Comman.hideLoading(context);
Comman.showSnakBar(Constants.servererror, context);
}
}
//request accept/reject popup
Future<void> acceptrejectpopup(String reqStatus) {
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Alert!',
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
content: new Text(reqStatus == '1'
? Constants.reqAcceptmessage
: Constants.reqRejectemessage),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: new Text(Constants.notxt),
),
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
Comman.showLoading(context);
acceptRejectRequest(reqStatus);
},
child: new Text(Constants.yestxt),
),
],
),
);
}
The provider is working just fine, the problem is when the provider notify the consumer the ListView updates the children, but the StatefulWidget check they're the same type (They're all RequestWidget) so they just update themselves (if you don't provide a key to the StatefulWidget they will try to check if they're the same element and update via the didChangeDependencies method), but you're updating the getData var in initState (which will call only once) so even if the consumer updates the value won't. Try it like this
#override
void initState() {
// TODO: implement initState
//getData = widget.data; not here
super.initState();
}
#override
void didChangeDependencies() {
// TODO: implement initState
getData = widget.data; //update it here
super.didChangeDependencies();
}
Other option would be just to give a specific key when building your widget in the itemBuilder so when the consumer updates it changes them accordingly
return RequestWidgets(key: ValueKey(modal.item[i].id),data: modal.item[i]);
// Or some value unique for each item
The problem here I guess is, in your Child widget class, since I can't see any requestId of the selected card being passed to the acceptdeclinerequest().
Your acceptdeclinerequest() expects two unique arguments to be passed when called:
String requestStatus
int requestId
If you look closely into the Child widget class, you are just passing requestStatus. I wonder from where are you getting this getData.id, and how is it identifying that some particular card is selected.
// look here, only requestStatus is being passed
onTap: () {
acceptrejectpopup('0');
}
// and here
onTap: () {
acceptrejectpopup('1');
}
And in your acceptRejectRequest, you are only passing requestStatus
acceptRejectRequest(reqStatus);
And then you call your acceptdeclinerequest() with this data
// machine is confused, where are we getting the getData.id
// it assumes the id as per it's need, hence the error
await product.acceptdeclinerequest(requestStatus, getData.id);
The machine is trying to figure out, which element you selected. Try to give the id from the selected card, and pass it to the method with correct getData.id of that particular element.
Suggestion: Pass in your id of the selected card when you are tapping on it, and then call your methods, and then pass it along to get the right requestId and remove it. Let your methods acceptrejectpopup() and acceptRejectRequest() accept the id of the selected item and then finally pass it to your acceptdeclinerequest()
// first step
onTap: () => acceptrejectpopup('0', your_card_reuqest_id);
// second step, pass data from the above popup method to acceptRejectRequest()
acceptRejectRequest(reqStatus, requestId);
//finally from acceptRejectRequest(reqStatus, requestId), pass it to the final method acceptdeclinerequest
acceptdeclinerequest(requestStatus, requestId);