RangeError in PageView inside a ListView - flutter

Following my last question at
Adding elements to List
I am trying to populate a PageView with the created PostMedia items.
Here you have the complete code for my StreamBuilder:
StreamBuilder<List<Post>>(
stream: postsProvider.posts,
builder: (context, snapshot) {
if (snapshot.data != null &&
snapshot.data.isNotEmpty &&
ConnectionState.done != null) {
List<Post> listaInicial = snapshot.data;
List<Post> listaFiltrada = [];
listaFiltrada = listaInicial;
return Padding(
padding: const EdgeInsets.all(0.0),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 140,
child: ListView.builder(
itemCount: listaFiltrada.length,
itemBuilder: (context, indexPost) {
bool es_ambassador =
listaFiltrada[indexPost].post_autor_is_ambassador;
//ver si el post tiene media
bool tiene_media =
listaFiltrada[indexPost].post_tiene_media;
bool tiene_fotos =
listaFiltrada[indexPost].post_tiene_fotos;
List<List<PostMedia>> lista_medios;
lista_medios = [];
if (tiene_fotos) {
//foto 1
var foto1 = listaFiltrada[indexPost].foto_1;
//incluimos foto1 en la lista
List<PostMedia> lista1 = [
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista1);
var foto2 = listaFiltrada[indexPost].foto_2;
if (foto2.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista2 = [
PostMedia(
media_url: foto2,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista2);
}
var foto3 = listaFiltrada[indexPost].foto_3;
if (foto3.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista3 = [
PostMedia(
media_url: foto3,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista3);
print("lISTA3:" +
lista_medios.length.toString());
}
var foto4 = listaFiltrada[indexPost].foto_4;
if (foto4.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista4 = [
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista4);
print("lISTA4:" +
lista_medios.length.toString());
}
var foto5 = listaFiltrada[indexPost].foto_5;
if (foto5.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista5 = [
PostMedia(
media_url: foto5,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista5);
}
}
var texto = listaFiltrada[indexPost].post_texto;
bool es_mipost = false;
var id_autor = listaFiltrada[indexPost].post_autor_id;
if (id_autor == _miId) {
es_mipost = true;
} else {
es_mipost = false;
}
bool estadenunciado =
listaFiltrada[indexPost].post_denunciado;
Timestamp recibida =
listaFiltrada[indexPost].post_fecha;
var fecha_recibida = "....";
if (recibida == null) {
} else {
DateTime date = DateTime.parse(
recibida.toDate().toString());
fecha_recibida =
DateFormat('yyyy-MM-dd HH:mm', "en")
.format(date);
fecha_recibida =
tiempoDesdeFecha(fecha_recibida);
}
return Padding(
padding:
const EdgeInsets.only(bottom: 2.0, top: 6),
child: Card(
elevation: 10,
margin: EdgeInsets.only(left: 0, right: 0),
child: Column(children: [
Row(
children: [
Column(
children: [
Padding(
padding:
const EdgeInsets.all(8.0),
child: Container(
width: 60,
height: 60,
color: Colors.transparent,
child: Stack(children: [
CircleAvatar(
radius: 31,
backgroundColor:
Colors.black,
child: CircleAvatar(
radius: 28.0,
backgroundImage: NetworkImage(
listaFiltrada[indexPost]
.post_autor_avatar),
backgroundColor:
Colors.transparent,
),
),
//si es ambassador el autor del post
es_ambassador
? Positioned(
right: 0,
bottom: 2,
child: Container(
height: 24,
width: 24,
child: Image(
image: AssetImage(
"assets/images/home_ambassador.png"))),
)
: Container(),
]),
),
),
],
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
listaFiltrada[indexPost]
.post_autor_nombre,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18),
),
Text(
fecha_recibida,
style: TextStyle(fontSize: 10),
),
Text(""),
Text(""),
],
),
Spacer(),
//si es mi post
es_mipost
? PopupMenuButton(
icon: Icon(
Icons.more_vert,
size: 36,
), //don't specify icon if you want 3 dot menu
color: AppColors.rojoMovMap,
itemBuilder: (context) => [
PopupMenuItem<int>(
value: 2,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
Text(
"Borrar post",
style: TextStyle(
fontWeight:
FontWeight
.bold,
fontSize: 16,
color: Colors
.white),
),
Icon(Icons.delete,
color: Colors
.white,
size: 30),
],
),
),
],
onSelected: (int indexx) async {
showConfirmacionBorradoPost(
context,
listaFiltrada[indexPost]
.post_id);
})
: PopupMenuButton(
icon: Icon(
Icons.more_vert,
size: 36,
), //don't specify icon if you want 3 dot menu
color: AppColors.rojoMovMap,
itemBuilder: (context) => [
PopupMenuItem<int>(
value: 1,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
!estadenunciado
? Text(
"denunciar",
style: TextStyle(
color: Colors
.white),
)
: Text(
"ya denunciado",
style: TextStyle(
fontSize:
14,
fontWeight:
FontWeight
.bold,
color: Colors
.white),
),
!estadenunciado
? Icon(
Icons
.local_police_outlined,
color: Colors
.white,
size: 30)
: Icon(
Icons
.local_police_outlined,
color: Colors
.white,
size: 30),
],
),
),
],
onSelected: (int indexx) async {
var postADenunciar =
listaFiltrada[indexPost]
.post_id;
PostCrud().denunciarPost(
postADenunciar);
Fluttertoast.showToast(
msg:
'has denunciado el post actual',
toastLength:
Toast.LENGTH_SHORT,
gravity:
ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor:
Colors.red,
textColor: Colors.white,
fontSize: 16.0);
DateTime now =
new DateTime.now();
DateTime date = new DateTime(
now.year,
now.month,
now.day,
now.hour,
now.minute,
now.second);
String fecha_denuncia = date
.day
.toString() +
"-" +
date.month.toString() +
"-" +
date.year.toString() +
" " +
date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
PostCrud().crearDenunciaPost(
postADenunciar,
_miId,
fecha_denuncia);
}),
//si no es mi post
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
width: MediaQuery.of(context)
.size
.width,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(8.0),
child: ReadMoreText(
texto,
trimLines: 1,
trimMode: TrimMode.Line,
trimCollapsedText:
"leer mas",
trimExpandedText:
'leer menos',
),
),
//si tiene media => mostrar el container, si no tiene media mostrar container en blanco
tiene_media
? Container(
color: Colors.black,
height: 200,
width: MediaQuery.of(
context)
.size
.width,
child: PageView.builder(
itemCount:
lista_medios
.length,
itemBuilder:
(BuildContext
context,
int indice) {
return GestureDetector(
onTap: () {
},
child:
Container(
margin:
const EdgeInsets.all(
0),
decoration:
BoxDecoration(
image:
DecorationImage(
image:
NetworkImage(lista_medios[indice][indexPost].media_url),
fit:
BoxFit.cover,
),
)),
);
}))
: Container(
color: Colors.red,
),
],
),
),
),
],
),
]),
),
);
}),
),
);
} else {
return Container(
height: 200,
width: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("assets/images/download.png"),
]),
);
}
})
Here you have a screenshot from the page:
The PageView is working fine, the current post has 4 images, that are shown in the pageView sliding on it on every page
In my app, the posts are ordered depending on their creation datetime, newer posts are shown on top of the list of posts and then the older posts are shown below the newest one.
At this app status, all posts are shown and ordered correctly, but the posts from #2 to the bottom of the list are getting an error at the pageview at this line:
NetworkImage(lista_medios[indice][indexPost].media_url),
the given error is RangeError (index): Invalid value: Only valid value is 0: 1

You are populating a bi-dimensional list with
List<List<PostMedia>> lista_medios;
When you just need a
List<PostMedia> lista_medios;
This is because you're already building the post, fetching media at a single indexPost and then building a List of media to display. In your example you will never have more than one element in the nested list but try to access more than one with indexPost.
Then calling NetworkImage(lista_medios[indice].media_url), should work.
Don't forget to modify the other parts of your code :
PostMedia media1 =
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false);
lista_medios.add(media1);
Your own solution is working because you're always fetching the single item of the nested list. For clarity you should not use a bi-dimensional list at all.

I think you are using indice instead indexPost by mistake. change their positions like this and I think it will work:
image:
NetworkImage(lista_medios[indexPost][indice].media_url),

I have found a potential solution. When creating the PostMedia list, it is only creating one item in the list, that means that the only way to get PostMedia items is using only the index called indice:
NetworkImage(lista_medios[indice][0].media_url)
The pageview is only showing elements from lista_medios, the indexPost index is not needed.

Your data shape looks like below for lista_medios
lista_medios = [
[PostMedia()], // lista1
[PostMedia()], // lista2
[PostMedia()], // lista3
[PostMedia()], // lista4
[PostMedia()], // lista5
];
Your lista_medios can contain between 0-5 lista in the list. While lista always has one PostMedia.
When you use:
NetworkImage(lista_medios[indice][indexPost].media_url)
you got an error because you might have more than one post and indexPost might be any value greater than 0 which again exceed the size of your lista which always has one element.
Solution to your problem
While NetworkImage(lista_medios[indice][0].media_url) would work but it's unnecessary complicated data shaped in this case.
You need to render at most 5 medias from each listaFiltrada. So you can simplify the shape like below (Remove unnecessary nested list):
// sample data shape for `lista_medios`: [PostMedia(), PostMedia(), ...]
List<PostMedia> lista_medios = [];
if (!listaFiltrada[indexPost].foto_1.isEmpty) {
lista_medios.add(PostMedia(
media_url: listaFiltrada[indexPost].foto_1,
es_foto: true,
es_video: false,
es_youtube: false
));
}
if (!listaFiltrada[indexPost].foto_2.isEmpty) {
lista_medios.add(PostMedia(
media_url: listaFiltrada[indexPost].foto_2,
es_foto: true,
es_video: false,
es_youtube: false
));
}
// and so on...
Then you can use it with PageViewBuilder like below:
return PageView.builder(
itemCount: lista_medios.length,
itemBuilder: (BuildContext context, int indice) {
return Container(
child: NetworkImage(lista_medios[indice].media_url)
);
}
);

Related

Why is clearing a List using setState not working for clearing my ListView as well?

I have the following code for populating a list which I use for a ListView
List<dynamic> menuItemList =[];
Future<void> displayItems() async {
// I recently tried setState here after trying it onTap() of menu option
setState(() {
menuItemList.clear();
});
var getMenuIDs =
await _dbRef.child('Categories/$categoryPath').onValue.listen((event) {
if (event.snapshot.value != null) {
//fetching all available records in orderTracker
var data = event.snapshot.value as Map;
//adding records to list
data.entries.map((e) => itemIDList.add(e.value)).toList();
//cycling through the list and extracting the orderID
var listLength = itemIDList.length;
for (var i = 0; i < listLength; i++) {
var listItem = itemIDList[i].toString();
itemID = listItem.substring(9, 12);
// trackerID = listItem.substring(12, 15);
allMenuDetail(itemID);
}
} else {
//Empty
}
});
}
Future<void> allMenuDetail(String itemID) async {
//List will be empty here
_streamSubscriber =
await _dbRef.child('MenuItem/$itemID').onValue.listen((event) {
//For some reason it repopulates here
if (event.snapshot.value != null) {
var data = event.snapshot.value as Map;
var price = data['price'];
var itemName = data['itemName'];
var desc = data['description'];
var itemImg = data['img'];
var item = data['id'];
Map<String, dynamic> myMap = {
'price': price,
'itemName': itemName,
'desc': desc,
'img': itemImg,
'itemID': itemID
};
List<dynamic> shortList = [myMap];
menuItemList.addAll(shortList);
setState(() {});
} else {
print('*********************=> HERE ');
}
});
}
I then display the menu item as follows:
ListView.builder(
itemCount: menuItemList.length,
itemBuilder: (context, index) {
final item = menuItemList[index];
var price = item['price'];
var itemName = item['itemName'];
var desc = item['desc'];
var itemImg = item['img'];
var itemID = item['itemID'];
return InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ItemPageState(
// path: categoryPath,
itemName: itemName,
desc: desc,
price: price,
itemImg: itemImg,
id: itemID)));
},
child: Card(
color: Colors.white,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipRect(
child: Align(
alignment: Alignment.center,
widthFactor: 0.8,
child: Image(
image: AssetImage(itemImg),
height: 100,
width: 150,
fit: BoxFit.cover,
),
)),
const SizedBox(width: 30),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
itemName,
style: const TextStyle(
color: Colors.black, fontSize: 25),
),
Text(
'Price: R' +
price.toString(), //+ price.toString(),
style: const TextStyle(
color: Colors.black, fontSize: 18),
),
],
),
const SizedBox(width: 30),
]),
));
},
)
I have a menu at the top of my page which will used to navigate to different categories of food. Once one of the icons is clicked I am able to fetch the relevant menu items but the list doesn't clear properly. I have use prints and it says the list has been emptied but on the GUI it just adds the other menu items at the bottom of the one first loaded
this is my menu code
Container(
height: 100,
child: ListView.separated(
padding: EdgeInsets.all(10),
separatorBuilder: (context, _) => const SizedBox(
width: 12,
),
scrollDirection: Axis.horizontal,
itemCount: categoryMenuList.length,
itemBuilder: (context, index) {
final catItem = categoryMenuList[index];
var catIgm = catItem['img'];
var title = catItem['title'];
var path = catItem['path'];
return Container(
width: 95,
height: 100,
child: Column(
children: [
Expanded(
child: AspectRatio(
aspectRatio: 4 / 3,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Material(
child: Ink.image(
image: AssetImage(catIgm),
fit: BoxFit.cover,
child: InkWell(
onTap: () {
//I did try set state here as well
categoryPath = path;
pageTitle = title;
displayItems();
setState(() {});
refreshPage();
},
),
))))),
const SizedBox(
height: 4,
),
Text(
title,
style: const TextStyle(
color: Colors.black, fontSize: 20),
)
],
),
);
}),
),
Sorry for the long winded post but I can't figure out where I'm going wrong

Graphql Pagination using flutter fetchmore callback calling builder method again which is refreshing my listview again from index 0

I am trying to use graphql_flutter plugin to fetch data from graphql API in this I have pagination using the offset if the offset is 0 it fetches the first 10 items if offset increases by 1 it fetches the next 10 so when I am using fetchmore callback method of graphql query it calls my builder method again which is refreshing my list again which I do not want I want once user scroll at maximum position data loads from that position only it should not reload my list and add data it just adds data to my list below is my query
my build method code
Widget build(BuildContext context) {
// TODO: implement build
MoengageFlutter.getInstance()
.trackEvent(StringConstants.loanListingScreen, null);
// Analytics().logCustomEvent(StringConstants.loanListingScreen, null);
// if (offset == 0) {
getLoanProducts =
"""query(\$offset: Int!, \$confKey: String!, \$operator: String!) {
user{
products(product_id: "",filter:[{
setting_type: "sort",
setting: [
{
conf_key: \$confKey,
operator: \$operator
},
]
}
], limit: 10, offset: \$offset) {
total
data{
id
name
loan_type
partner_id
internal_rating
playstore_rating
interest_rate_min
interest_rate_max
loan_amount_min
loan_amount_max
processing_fee_percentage
processing_fee_min
processing_fee_max
required_documents
duration_min
duration_max
duration_min_unit
duration_max_unit
app_url
partner {
id
name
logo
short_description
term_link
support_contact
support_email
}
}
}
}
}""";
// }
// productListingBloc.getProductListingBloc(getLoanProducts).then((value){
// print(value);
// });
return Query(
options: QueryOptions(document: gql(getLoanProducts), variables: {
"offset": offset,
"confKey": confKey,
"operator": _operator
}),
builder: (QueryResult result,
{VoidCallback refetch, FetchMore fetchMore}) {
if (result.hasException) {
return Container(
width: ResponsiveRenderer().widthOfItem(context),
child: UnknownErrorState(
errorImage: StringConstants.unknownErrorStateImage,
text: StringConstants.unknownErrorStateText,
titleText: StringConstants.unknownErrorOccurredText,
));
}
if (result.isLoading) {
return offset == 0
? Center(
child: CircularProgressIndicator(),
)
: Center(child: CircularProgressIndicator());
}
return Scaffold(
backgroundColor: Colors.white,
appBar: !_isAppBar
? AppBar(
automaticallyImplyLeading: false,
elevation: 0,
backgroundColor: HexColor(ColorConstants.white),
)
: AppBar(
automaticallyImplyLeading: false,
title: Container(
child: TextCustom(
text: StringConstants.loansText,
textAlign: TextAlign.left,
hexColor: ColorConstants.black,
fontSize: DimensionConstants.textSize18,
fontWeight: FontWeight.w500,
),
),
actions: [
InkWell(
onTap: () {
MoengageFlutter.getInstance().trackEvent(
StringConstants.sortButtonLoanListing, null);
Analytics().logCustomEvent(
StringConstants.sortButtonLoanListing, null);
showSortBottomSheet(refetch);
},
child: Container(
margin: EdgeInsets.only(
right: DimensionConstants.margin16,
top: DimensionConstants.margin12),
child: Row(
children: [
Container(
margin: EdgeInsets.only(
right: DimensionConstants.margin8),
child:
SvgPicture.asset("assets/loan/sort.svg"),
),
TextCustom(
text: StringConstants.sortText,
hexColor: ColorConstants.black,
fontSize: DimensionConstants.textSize14,
),
],
),
),
)
],
elevation: 0,
backgroundColor: Colors.white,
),
body: BlocBuilder<NetworkBloc, NetworkState>(
builder: (context, state) {
if (state is ConnectionFailure) {
return Center(
child: UnknownErrorState(
errorImage: StringConstants.noInternetStateImage,
titleText: StringConstants.noInternetStateText,
text: StringConstants.noInternetStateSubText,
),
);
}
ProductListing listData = ProductListing.fromJson(result.data);
if (listData.user.products == null ||
listData.user.products.data.length == 0) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Image.asset(
"assets/loan/loan_error_state.png",
width: ResponsiveRenderer().widthOfItem(context) *
0.8,
height:
ResponsiveRenderer().heightOfItem(context) *
0.4,
),
),
Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants.noLoansFoundText,
fontSize: DimensionConstants.textSize16,
fontWeight: FontWeight.w500,
),
),
Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants
.checkLocationAndPersonalDetails,
fontSize: DimensionConstants.textSize12,
hexColor: ColorConstants.greyDark,
),
),
InkWell(
onTap: () {
MoengageFlutter.getInstance().trackEvent(
StringConstants.editProfileButtonLoanListing,
null);
Analytics().logCustomEvent(
StringConstants.editProfileButtonLoanListing,
null);
Navigator.of(context)
.pushNamed(MyProfile.routeName,
arguments: MyProfile(
phoneNumber: null,
));
},
child: Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants.editProfileText,
fontSize: DimensionConstants.textSize14,
fontWeight: FontWeight.w500,
hexColor: ColorConstants.blue,
),
),
),
],
),
),
);
} else {
_scrollController
..addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (total > current) {
offset++;
FetchMoreOptions opts = FetchMoreOptions(
variables: {
'offset': offset,
"confKey": confKey,
"operator": _operator
},
document: gql(getLoanProducts),
updateQuery:
(previousResultData, fetchMoreResultData) {
print(fetchMoreResultData);
final List<dynamic> repos = [
...previousResultData['user']['products']
['data'] as List<dynamic>,
...fetchMoreResultData['user']['products']
['data'] as List<dynamic>
];
fetchMoreResultData['user']['products']['data'] =
repos;
productList = repos;
return fetchMoreResultData;
},
);
fetchMore(opts);
}
}
});
current =
(offset * limit) + listData.user.products.data.length;
total = listData.user.products.total;
return Column(
children: [
// Container(
// margin: EdgeInsets.only(
// left: DimensionConstants.margin12,
// top: DimensionConstants.margin16),
// child: TextCustom(
// text: StringConstants.loanListingDescription,
// textAlign: TextAlign.left,
// fontSize: DimensionConstants.margin14,
// hexColor: ColorConstants.black,
// ),
// ),
// _sortAndFilter(),
Expanded(
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: listData.user.products.data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.only(
top: DimensionConstants.margin16,
bottom: DimensionConstants.margin16),
child: CardCustomLoanList(
listData: listData, index: index));
}),
),
],
);
}
}));
});
}```
here fetch more calling builder methods again which is refreshing my whole list I don't want to refresh my whole list
Please anyone knows any better way to do this?
[1]: https://pub.dev/packages/graphql_flutter

Flutter - Image.memory not refreshing after source change

I have a page that allows users to upload documents (as images). I have structured my page in a way that for each document type that can be uploaded a Document_Upload widget is used to reduce the amount of repeated code.
On initial load I use a FutureBuilder to get all the documents the user has already uploaded from our REST Api and then populate each Document_Upload widget with the relevant data.
On successful upload our REST Api returns the new image back to the Flutter app as a Byte Array so it can be displayed.
The problem I am currently facing is that no matter what I try the image widget (Image.memory) does not display the new image, it just stays on the old one.
I have tried almost everything I can think of/ find online to resolve this issue, including:
Calling setState({}); after updating the imageString variable - I can see the widget flash but it remains on the original image.
Using a function to callback to the parent widget to rebuild the entire child widget tree - same result as setState, all the widgets flash, but no update.
Calling imageCache.clear() & imageCache.clearLiveImages() before updating the imageString.
Using CircleAvatar instead of Image.memory.
Rebuilding the Image widget by calling new Image.memory() inside the setState call.
I am starting to question if this is an issue related to Image.memory itself, however, using Image.File / Image.network is not an option with our current requirement.
Refreshing the page manually causes the new image to show up.
My code is as follows:
documents_page.dart
class DocumentsPage extends StatefulWidget {
#override
_DocumentsPageState createState() => _DocumentsPageState();
}
class _DocumentsPageState extends State<DocumentsPage>
with SingleTickerProviderStateMixin {
Future<Personal> _getUserDocuments;
Personal _documents;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getUserDocuments = sl<AccountProvider>().getUserDocuments();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: SafeArea(
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: Container(
constraints: BoxConstraints(maxWidth: 1300),
child: buildFutureBuilder(context)),
)),
),
);
}
Widget buildFutureBuilder(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
return FutureBuilder<Personal>(
future: _getUserDocuments,
builder: (context, AsyncSnapshot<Personal> snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
} else {
if (snapshot.data == null) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
_documents = snapshot.data;
return Column(
children: [
SizedBox(height: 20.0),
Text(
"DOCUMENTS",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: AppColors.navy),
),
Container(
constraints: BoxConstraints(maxWidth: 250),
child: Divider(
color: AppColors.darkBlue,
height: 20,
),
),
Container(
margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Text(
"These documents are required in order to verify you as a user",
style: TextStyle(fontSize: 14))),
Container(
margin: EdgeInsets.only(bottom: 25.0),
child: Text("View our Privacy Policy",
style: TextStyle(fontSize: 14))),
Container(
child: screenSize.width < 768
? Column(
children: [
DocumentUpload(
imageType: "ID",
imageString: _documents.id),
DocumentUpload(
imageType: "Drivers License Front",
imageString: _documents.driversLicenseFront,
),
DocumentUpload(
imageType: "Drivers License Back",
imageString: _documents.driversLicenseBack,
)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DocumentUpload(
imageType: "ID",
imageString: _documents.id),
DocumentUpload(
imageType: "Drivers License Front",
imageString: _documents.driversLicenseFront,
),
DocumentUpload(
imageType: "Drivers License Back",
imageString: _documents.driversLicenseBack,
),
])),
Container(
child: screenSize.width < 768
? Container()
: Padding(
padding:
EdgeInsets.only(top: 10.0, bottom: 10.0))),
Container(
child: screenSize.width < 768
? Column(
children: [
DocumentUpload(
imageType: "Selfie",
imageString: _documents.selfie,
),
DocumentUpload(
imageType: "Proof of Residence",
imageString: _documents.proofOfResidence,
),
Container(width: 325)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DocumentUpload(
imageType: "Selfie",
imageString: _documents.selfie,
),
DocumentUpload(
imageType: "Proof of Residence",
imageString: _documents.proofOfResidence,
),
Container(width: 325)
])),
],
);
}
}
});
}
}
document_upload.dart
class DocumentUpload extends StatefulWidget {
final String imageType;
final String imageString;
const DocumentUpload({this.imageType, this.imageString});
#override
_DocumentUploadState createState() => _DocumentUploadState();
}
class _DocumentUploadState extends State<DocumentUpload> {
String _imageType;
String _imageString;
bool uploadPressed = false;
Image _imageWidget;
#override
Widget build(BuildContext context) {
setState(() {
_imageType = widget.imageType;
_imageString = widget.imageString;
_imageWidget =
new Image.memory(base64Decode(_imageString), fit: BoxFit.fill);
});
return Container(
constraints: BoxConstraints(maxWidth: 325),
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
new BoxShadow(
color: AppColors.lightGrey,
blurRadius: 5.0,
offset: Offset(0.0, 3.0),
),
],
),
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Column(children: <Widget>[
Padding(padding: EdgeInsets.only(top: 5.0)),
Row(
//ROW 1
children: <Widget>[
Expanded(
child: Text(
_imageType,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.darkBlue),
),
),
],
),
Row(
//ROW 2
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(left: 5.0, bottom: 5.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: _imageWidget,
)),
),
Consumer<AccountProvider>(
builder: (context, provider, child) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Icon(Icons.star,
size: 20, color: AppColors.darkBlue)),
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: Text('Drag file here or',
textAlign: TextAlign.center)),
Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0),
child: DynamicGreyButton(
title: uploadPressed
? "Uploading ..."
: "Browse",
onPressed: () async {
FilePickerResult result =
await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: [
'jpg',
'jpeg',
'png'
]);
if (result != null) {
uploadPressed = true;
Uint8List file =
result.files.single.bytes;
String fileType =
result.files.single.extension;
await provider
.doUploadDocument(
_imageType, file, fileType)
.then((uploadResult) {
if (uploadResult == null ||
uploadResult == '') {
showToast(
"Document failed to upload");
return;
} else {
showToast("Document uploaded",
Colors.green, "#66BB6A");
uploadPressed = false;
_imageString = uploadResult;
setState(() {});
}
});
} else {
// User canceled the picker
uploadPressed = false;
}
},
))
]));
})
],
),
])));
}
}
Image Upload HTTP Call
#override
Future uploadDocuments(DocumentsUpload model) async {
final response = await client.post(
Uri.https(appConfig.baseUrl, "/api/Account/PostDocuments_Flutter"),
body: jsonEncode(model.toJson()),
headers: <String, String>{
'Content-Type': 'application/json'
});
if (response.statusCode == 200) {
var data = json.decode(response.body);
return data;
} else {
return "";
}
}
EDIT: Attached GIF of current behaviour.
I am pretty much out of ideas at this point, any help would be greatly appreciated.
Came up with a solution.
I created a second variable to hold the new image string and showed an entirely new image widget once the second variable had value.
String _newImage;
In the success of the upload...
_newImage = uploadResult;
setState(() {});
Image widget...
child: (_newImage == null || _newImage == '')
? new Image.memory(base64Decode(_imageString), fit: BoxFit.fill)
: new Image.memory(base64Decode(_newImage), fit: BoxFit.fill)
Not a very elegant solution, but it's a solution, but also not necessarily the answer as to why the original issue was there.

How to make the content collapse by a button in Flutter?

I have such a column, consisting as it were of the title and the content created via List.generate. How I can create an animation of collapsing this list into a title, actually by clicking on the title itself?
Column(
children: [
GestureDetector(
onTap: () {},
child: Container(
color: Theme.of(context).sliderTheme.inactiveTrackColor,
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Center(
child: Text('${category.name}',
style: Theme.of(context).textTheme.headline6.copyWith(
fontSize: 16,
color: Theme.of(context).selectedRowColor)),
),
),
),
),
Column(
children: List.generate(category.items.length, (index) {
return Container(
decoration: BoxDecoration(
border: (index + 1) != category.items.length
? Border(
bottom: BorderSide(
color: Style.inactiveColorDark.withOpacity(1.0),
width: 0.5))
: null),
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Color(int.parse(category.color)),
shape: BoxShape.circle),
),
SizedBox(
width: 12,
),
Text('${category.items[index].description}',
style: Theme.of(context)
.primaryTextTheme
.headline1
.copyWith(fontSize: 16)),
],
),
SwitchButton(
isActive: category.items[index].isAdded,
activeColor:
Theme.of(context).sliderTheme.activeTrackColor,
inactiveColor:
Theme.of(context).sliderTheme.inactiveTrackColor,
activeCircleColor:
Theme.of(context).sliderTheme.activeTickMarkColor,
inactiveCircleColor:
Theme.of(context).sliderTheme.inactiveTickMarkColor,
turnOn: () {
ChosenFeeling removingElement = ChosenFeeling(
id: 000,
isAdded: false,
);
// If chosen list is empty
if (chosenfeelings.isEmpty) {
chosenfeelings.add(ChosenFeeling(
isAdded: true, id: category.items[index].id));
} else {
// If user tap on switchButton 2 times
chosenfeelings.removeWhere((element) {
if (element.id != null &&
element.id == category.items[index].id) {
removingElement = element;
}
return _isNeedToRemoveWhenOn(
currentItem: category.items[index],
listItem: element,
);
});
// If list isn`t empty and chosen item isn`t in list
if (category.items[index].id != removingElement.id) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: true,
));
}
}
},
turnOff: () {
ChosenFeeling removingElement = ChosenFeeling(
id: 000,
isAdded: false,
);
if (chosenfeelings.isEmpty) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: false,
));
} else {
// If user tap on switchButton 2 times
chosenfeelings.removeWhere((element) {
if (element.id != null &&
element.id == category.items[index].id) {
removingElement = element;
}
return _isNeedToRemoveWhenOff(
currentItem: category.items[index],
listItem: element,
);
});
// If list isn`t empty and chosen item isn`t in list
if (category.items[index].id != removingElement.id) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: false,
));
}
}
},
)
],
),
),
);
}),
)
],
);
The easiest way is to use expandable package for this purpose:
https://pub.dev/packages/expandable
Create expandable controller outside of build method
final ExpandableController _expandableController = ExpandableController();
And then use it like this:
ExpandableNotifier(
controller: _expandableController,
child: Column(
children: [
Expandable(
collapsed: ExpandableButton(
child: titleWidget(),
),
expanded: Column(
children: [
ExpandableButton(
child: titleWidget(),
),
list(),
]
),
),
],
),
);
Don't forget to dispose controller after using it
#override
void dispose() {
_expandableController.dispose();
super.dispose();
}
Hope this helps. You can also create your own animation with a little research

How to fetch a list of maps to a calendar flutter

I have a response API here -
{
"code": 0,
"message": "All the revisions of current user ",
"data": [
{
"id": 15,
"box_id": 31,
"user_id": 53,
"revision_type": "1",
"revision_date": "2021-05-30",
"revision_location": "gafsa",
"revision_title": "Une visite technique est important avant le 30-05-2021",
"kilometrage_pour_vidange": null,
"repeat_revision": 0,
"revision_status": 0,
"kilometrage_last_vidange": null,
"Kilometrage_revision": null
},
{
"id": 16,
"box_id": 31,
"user_id": 53,
"revision_type": "0",
"revision_date": "2021-06-26",
"revision_location": "tyyu",
"revision_title": "ygyyii",
"kilometrage_pour_vidange": 8655,
"repeat_revision": 0,
"revision_status": 0,
"kilometrage_last_vidange": null,
"Kilometrage_revision": null
},
{
"id": 17,
"box_id": 31,
"user_id": 53,
"revision_type": "2",
"revision_date": "2021-06-20",
"revision_location": "STAR",
"revision_title": "Votre prochain renovellement de l'assurance sera le 20-06-2021 avec l'agence STAR",
"kilometrage_pour_vidange": null,
"repeat_revision": 0,
"revision_status": 0,
"kilometrage_last_vidange": null,
"Kilometrage_revision": null
},
{
"id": 18,
"box_id": 31,
"user_id": 53,
"revision_type": "3",
"revision_date": "2021-06-20",
"revision_location": "sfax",
"revision_title": "véhicule en panne",
"kilometrage_pour_vidange": null,
"repeat_revision": 0,
"revision_status": 0,
"kilometrage_last_vidange": null,
"Kilometrage_revision": 87654
}
],
"error": [],
"status": 200
}
I can show event already exist from my API to Table Calendar dynamically with no problem , All things work fine . But I would like to display more than one variable on my card . At the moment i can display only "revisionLocation" . How i can fecth other veriable to the screen
my code :
class _RevisionState extends State<Revision> with TickerProviderStateMixin {
//debut code events
CalendarController _controller;
Map<DateTime, List<dynamic>> _events;
List<dynamic> _selectedEvents;
TextEditingController _eventController, dateController;
SharedPreferences prefs;
int status;
bool _autovalidate = false;
int status1 = 0;
// final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final _formKey = GlobalKey<FormState>();
RevisionApi revisionApi = RevisionApi();
TextEditingController _Kilometrage_revisionController =
TextEditingController();
TextEditingController _KilometrageController = TextEditingController();
TextEditingController _EmplacementController = TextEditingController();
TextEditingController _DateController = TextEditingController();
/* TextEditingController _repeat_revisionController =
TextEditingController(text: "non");*/
TextEditingController _revision_titleController = TextEditingController();
TextEditingController _revision_agenceController = TextEditingController();
// TextEditingController _eventController = TextEditingController();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
getTask1().then((val) => setState(() {
_events = val;
}));
//print( ' ${_events.toString()} ');
});
super.initState();
_controller = CalendarController();
_eventController = TextEditingController();
_events = {};
initializeDateFormatting();
_selectedEvents = [];
prefsData();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onDaySelected(DateTime day, List events) {
// print('CALLBACK: _onDaySelected');
setState(() {
_selectedEvents = events;
});
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return SafeArea(
minimum: const EdgeInsets.only(top: 20.0),
child: Scaffold(
backgroundColor: Color(0xFF050127),
appBar: AppBar(
backgroundColor: Color(0xFF010611),
iconTheme: IconThemeData(color: Colors.white),
automaticallyImplyLeading: true,
centerTitle: true,
title: Text(
widget.title = 'Révision',
style: TextStyle(
color: Colors.white,
),
//textHeightBehavior: ,
),
elevation: 0.0,
leading: GestureDetector(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SideBar()));
},
child: Icon(CommunityMaterialIcons.segment),
),
actions: [
Icon(
Icons.search, /*color: Colors.black87*/
),
SizedBox(
width: 10,
),
]),
body: SingleChildScrollView(
child: Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.only(left: 10),
height: size.height * 0.05,
width: size.width * 1,
decoration:
BoxDecoration(color: Colors.white.withOpacity(0.2)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
DateFormat("dd-MM-yyyy hh:mm:ss")
.format(DateTime.now()),
style: TextStyle(
color: Colors.white,
fontSize: 16,
letterSpacing: 1),
),
IconButton(
icon: Icon(
CommunityMaterialIcons.calendar_plus,
color: KYellow,
),
onPressed: () {
_showAddDialog();
})
],
),
)
],
),
Row(
children: [
Expanded(
child: (_buildTableCalendarWithBuilders()),
),
],
),
_buildEventList(),
],
),
)),
),
);
}
_showAddDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
title: Text("Ajouter un évènement"),
content: StatefulBuilder(builder: (
BuildContext context,
StateSetter setState,
) {
return SingleChildScrollView(
//
child: Form(
key: _formKey,
autovalidate: _autovalidate,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 10),
child: DropdownButtonFormField(
decoration: InputDecoration(
hoverColor: Colors.white,
/* contentPadding: EdgeInsets.only(
left: 10, right: 15, top: 15),*/
labelText: 'Type',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
dropdownColor: Colors.white,
value: status,
items: <DropdownMenuItem>[
DropdownMenuItem(
// value: 'videnge',
value: 0,
child: InkWell(
child: Text('Vidange'),
hoverColor: Colors.indigo,
),
),
DropdownMenuItem(
// value: 'visite technique',
value: 1,
child: Text('Visite technique'),
),
DropdownMenuItem(
// value: 'assurance véhicule',
value: 2,
child: Text('Assurance véhicule'),
),
DropdownMenuItem(
// value: 'autre',
value: 3,
child: Text('Autre'),
),
],
onChanged: (value) {
setState(() {
status = value;
});
},
),
)),
]),
if (status == 0) vidangeDropdown(),
if (status == 1) visiTechniqueDropdown(),
if (status == 2) assuranceDropdown(),
if (status == 3) autresDropdown(),
actions: <Widget>[
TextButton(
child: Text(
"Enregistrer",
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
),
onPressed: () {
if (_eventController.text.isEmpty == null) return;
setState(() {
if (_events[_controller.selectedDay] != null) {
_events[_controller.selectedDay]
.add(_eventController.text);
} else {
_events[_controller.selectedDay] = [
_eventController.text
];
}
prefs.setString(
"events", json.encode(encodeMap(_events)));
_eventController.clear();
setRevision();
// Navigator.of(context).pop();
// Navigator.pop(context);
});
// Navigator.of(context).pop();
},
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Retour'),
),
],
));
setState(() {
_selectedEvents = _events[_controller.selectedDay];
});
}
Widget _buildEventList() {
return Column(children: [
..._selectedEvents.map(
(event) => Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
CommunityMaterialIcons.oil,
color: KYellow,
),
SizedBox(
width: 10,
),
Text(
event,
style: TextStyle(
color: KYellow,
fontSize: 16,
fontWeight: FontWeight.w500),
)
],
),
SizedBox(
height: 20,
),
Text(
'Votre véhicule atteint 45 000 Km un vidange est important'),
SizedBox(
height: 20,
),
Text(
'Dernier visite effectuée le 23/12/2020',
style: TextStyle(color: Colors.indigo[400]),
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
CommunityMaterialIcons.file_clock,
size: 35,
color: KYellow,
),
onPressed: () {},
),
SizedBox(
width: 30,
),
IconButton(
icon: Icon(
CommunityMaterialIcons.content_save_edit_outline,
size: 35,
color: KYellow,
),
onPressed: () {
if (event == 'Vidange') {
showDialog(
context: context,
builder: (BuildContext context) =>
_buildUpdateVidengeEvent(context),
);
} else
showDialog(
context: context,
builder: (BuildContext context) =>
_buildUpdateEvent(context),
);
},
)
],
)
],
))),
),
)
]);
}
Future<Map<DateTime, List>> getTask1() async {
Map<DateTime, List> mapFetch = {};
List<Datum> event = await revisionApi.getAllRevision();
for (int i = 0; i < event.length; i++) {
var createTime = DateTime(event[i].revisionDate.year,
event[i].revisionDate.month, event[i].revisionDate.day);
var original = mapFetch[createTime];
if (original == null) {
print("null");
mapFetch[createTime] = [event[i].revisionLocation];
} else {
// print(event[i].revisionLocation);
mapFetch[createTime] = List.from(original)
..addAll([event[i].revisionLocation]);
}
}
print(mapFetch);
return mapFetch;
}
}
and this is the screen contain list of cards :
I would like to add "revision_title" and "revision_date" instead static string in the body of the card . How i can do that ?
thanks in advance
I don't know what the selectedEvents contain but if you want to use several parameters you should probably convert your data to a Dart class or a Map structure that encapsulates all the data required to build those widgets and use those parameters properly where needed.
From what the I see the map is for changing each element of the list into another one. If your events are the elements [revision_location, revision_title] you are actually building two widgets one with the first parameter and a second one with the second parameter. So if you want to display a single element you need to build a single widget with the elements at each index. And if you want to build several items you need to iterate through a List<List> or List<Map> or List<DartClassWithData> to fetch all data for each widget you build
➕ Also if for some reason the app becomes international you may consider using the intl official package to use DateFormat to format the dates according to each country, as long as it is local you may directly write the string you get the specific way you require.
I hope you find a solution and if you can't. At least add extra information about what problem you have fetching all the information inside a single encapsulated class 🤗.