Flutter: ListView.builder + MultiImagePicker2 - android

Im currently working on an app where it gets the pictures from galleries, then listing them out. But i cant seem to get it right. Currently facing an issue where i get the error
"_TypeError (type 'Future' is not a subtype of type 'Widget')".
any ideas
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:multi_image_picker2/multi_image_picker2.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Asset> claims = <Asset>[];
final ImagePicker imgpicker = ImagePicker();
List<Asset>? imagefiles;
loadLimitedImages() async {
try {
var pickedfiles = await MultiImagePicker.pickImages(maxImages: 3);
if (pickedfiles != null) {
setState(() {
imagefiles = pickedfiles;
});
} else {
print("No image is selected.");
}
} catch (e) {
print("error while picking file.");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
'Testing Functions',
style: TextStyle(color: Colors.black),
),
centerTitle: true,
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
imagefiles != null ? loadLimitedImages() : Container(),
imagefiles?.length == 3
? Container()
: IconButton(
onPressed: () => loadLimitedImages(),
icon: Icon(Icons.camera_enhance),
iconSize: 100,
),
],
),
),
),
),
);
}
}
I can only have 3 max images and it must be stacked in a row, when the images are picked, it goes back to the screen. If 3 images are showing, the camera icon dissapears, if not it will be beside the picked images if less than 3.

You get this error because of the following line in your build() method.
imagefiles != null ? loadLimitedImages() : Container(),
Here you call loadLimitedImages() which will return a Future, which is not a Widget. That method does not return anything, so this won't work in the way you try to use it.
If I understand it correctly, you want to pick 3 images, that operation should be a result of an action, e.g. a user taps a button to pick images. That is where you can call your loadLimitedImages() method. You shouldn't call such a method inside the build() method, since it could run frequently. The build() method's purpose is to build the UI based on the current state.
If you need to pick images without user interaction you can initiate it in the initState() of your State.

Related

Is there a way to use the same globalkey in multiple widgets?? in flutter

So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code

Change random background image by clicking a button flutter (dart)

I have a list of images, and a function that picks an image from that list randomly:
AssetImage imagePicker() {
Random randomNumberGen = Random();
int index = randomNumberGen.nextInt(bgImgList.length);
return AssetImage(bgImgList[index]);
}
And I want a button that when clicking it will call this function and refresh the screen.
floatingActionButton: FloatingActionButton(
onPressed: () { imagePicker(); },
child: const Text(
'change picture' ,
textAlign: TextAlign.center,
),
The issue is the function is called, but the widget i have is not refreshing so the picture doesn't change
this is the widget code:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Israel Geography'),
centerTitle: true,
backgroundColor: Colors.blue[900],
),
body: Center(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imagePicker(),
fit: BoxFit.cover
),
),
)
),
floatingActionButton: FloatingActionButton(
onPressed: () { imagePicker(); },
child: const Text(
'change picture' ,
textAlign: TextAlign.center,
),
),
);
}
Technically, you are calling the imagePicker() method twice, and there is also no state that is holding the final picked image.
Also, this makes the screen not static anymore. The displayed image is changing on each button click, so there is dynamic information in your UI now, so you need to convert your Stateless widget into a Stateful one so you can do setState() whenever the visible information changes.
So after converting to Stateful,
your State class should have a variable like
AssetImage pickedImage = AssetImage(...); // a default image
And in your imagePicker() method, you can assign the pickedImage var with the chosen image instead of returning it.
AssetImage imagePicker() {
Random randomNumberGen = Random();
int index = randomNumberGen.nextInt(bgImgList.length);
// this will rebuild your UI
setState(() {
pickedImage = AssetImage(bgImgList[index]);
});
}
And in your widget, instead of this:
image: imagePicker(),
Do this:
image: pickedImage,
And every time on button click, you pick a new image, rebuild the UI because of setState and now pickedImage will be pointing to another image.
You need the state for a random image. StatefulWidget is one way to accomplish that.
class ImagePicker {
static Image random() {
return Image.network('https://picsum.photos/500/300?andom=${DateTime.now().millisecondsSinceEpoch}');
}
}
class ImagePickerWidget extends StatefulWidget {
const ImagePickerWidget();
#override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
Image _random = ImagePicker.random();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: _random),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _random = ImagePicker.random()),
child: const Icon(Icons.refresh),
),
);
}
}
If you want to keep a widget stateless, provider is one way to that. See Simple app state management for details.

Flutter setState executing but not rerendering UI when setting parent stateless widget flag

My app has an introductory feature where it simply informs the user on an action to take, the issue is this help action text (Container(...)) does not get removed one the setState() function is called.
Logical overview of process:
-> `User launches app`
|-> `login`
|-> `show main UI (with help action if first time launch)`
|-> first time launch ? show help text : don't show
| User acknowledges help text, set in preferences
Below are some code snippets of the dart fragments
UiHomePage (main UI - this is the parent UI)
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
#override
Widget build(BuildContext context) {
Widget pageDashboardUser() {
...
// Notify UiComponentPartnerSelector if we should show help action text based on AppSharedPreferences().isFirstTap()
Widget middleBrowseCard() {
return new FutureBuilder(
builder: (context, snapshot) {
return UiComponentPartnerSelector(
_displayProfiles, snapshot.data);
},
future: AppSharedPreferences().isFirstTap());
}
var search = topSearch();
var selector = middleBrowseCard();
return Stack(
children: [search, selector],
);
return Scaffold(...)
}
This Widget displays a bunch of profiles with a base card, a text overlay, and a hint text component.
The main focus is showHint define in the constructur (true if the app is launched for the first time), showTapTutorial() which either returns the hint component or an empty container and finally the _onTap(Profile) which handles the onclick event of a card.
UiComponentPartnerSelector (sub UI - the help text is shown here
class UiComponentPartnerSelector extends StatefulWidget {
bool showHint;
final List<Profile> items;
UiComponentPartnerSelector(this.items, this.showHint, {Key key})
: super(key: key);
#override
_UiComponentPartnerSelector createState() => _UiComponentPartnerSelector();
}
class _UiComponentPartnerSelector extends State<UiComponentPartnerSelector> {
UiComponentCard _activeCard;
int _tappedImageIndex = 0;
Widget showTapTutorial() {
if (!widget.showHint) {
return Container();
}
return Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 32),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.touch_app,
color: Colors.black.withOpacity(0.6),
),
Text(
"Touch to view partner profile",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
)
],
),
);
}
#override
Widget build(BuildContext context) {
Color _standard = Colors.white;
//
// _cache = widget.items.map((e) => {
// e.imageUri.toString(),
// Image.network(e.imageUri.toString())
// });
Future _onTap(Profile e) async {
if (!widget.showHint) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => UiViewProfile(e)));
} else {
AppSharedPreferences().setFirstTap(false).then((value) {
setState(() {
widget.showHint = false;
});
});
}
}
UiComponentCard createComponentCard(Profile e) {
...
return UiComponentCard(
onTap: () {
_onTap(e);
},
wImage: Center(
child: Image.network(
e.profileImageLink.toString(),
fit: BoxFit.fill,
),
),
wContent:
// Center(
// child: UiTextLine(text: e.displayName),
// ),
Column(
children: [
topBasicInfo(),
Expanded(child: Container()),
showTapTutorial(),
Expanded(child: Container()),
bottomBio()
],
),
);
}
return Container(
child: Stack(...)
);
Problem:
When _onTap(Profile) is clicked and showHint is true.
What should happen:
What SHOULD happen next is AppSharedPreferences().setFirstTap(false) should set the initial tap flag to false, then when finished setState() including setting showHint to false, then rerendering the UI and removing the hint text container (found in showTapTutorial()).
What happens:
What infact happens is when _onTap() is called, it updates the preferences correctly, setState() is called and showHint == false and !widget.showHint in showTapTutorial() is true returning Container() BUT the UI itself doesn't rerender.
Thus after clicking this "button" for the first time, the UI remains (doesn't change). Clicking a second time executes the Navigator.of(context).push(MaterialPageRoute(builder: (context) => UiViewProfile(e))); part WHILE the action help text (tutorial) is still showing. If I click on the same card again
Am I missing something or doing something wrong?

How to fix flutter widget flickering?

I'm trying to load an image from firebase storage to my app however I have this weird issue where the profile page(where this image is loading) keeps flickering. The image is loading fine however the whole widget keeps flickering. I have narrowed the issue down to the setState() called within the function getProfilePic() after some debugging, however I do not know if it's the function itself or my call to said function.
P.S there is no issue with the fileURL or picRef.getDownloadURL() as I've tested this with a random internet image as well and got the same flickering.
class profileTab extends StatefulWidget {
#override
_profileTabState createState() => _profileTabState();
}
class _profileTabState extends State<profileTab> {
User user = FirebaseAuth.instance.currentUser;
String _image = "https://picsum.photos/250?image=9";
Reference picRef = FirebaseStorage.instance.ref().child(FirebaseAuth.instance.currentUser.uid);
Future<Widget> getProfilePic() async {
await picRef.getDownloadURL().then((fileURL){
setState(() {
_image = fileURL;
});
});
}
#override
Widget build(BuildContext context) {
getProfilePic();
return StreamBuilder(
stream: FirebaseFirestore.instance.collection('users').doc(user.uid).snapshots(),
builder: (context, snapshot){
if (snapshot.connectionState == ConnectionState.active){
return ListView(
children: <Widget>[
SizedBox(height: 100.0,),
CircleAvatar(
radius: 100.0,
backgroundColor: Colors.lightBlueAccent,
child: ClipOval(
child: SizedBox(
width: 180.0,
height: 180.0,
child: Image.network(_image,fit: BoxFit.fill,),
),
),
),
SizedBox(height: 30.0,),
Center(child: Text("Name: " + snapshot.data.data()['name'],textScaleFactor: 3.0,)),
]
);
}
else {
return CircularProgressIndicator();
}
},
);
}
}
getProfilePic is redrawing widget by calling setState.
setState calls build method which calls getProfilePic.
Therefore, when first time build method is called we call getProfilePic which again updates widget tree.
Fix: Inside getProfilePic add check to call setState if _image is null which will redraw widget only once.
It would be better if you use Image.network. You can refer this
https://www.woolha.com/tutorials/flutter-display-image-from-network-url-show-loading

How to implement a swipe to delete listview to remove data from firestore

Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore

Categories

Resources