Flutter 2 second shows error when page load and ater 2 secons successfully shows - image

Hello Any One Flutter Expert here ?? plz help meee...what i want to make is Like Facebook photo viewer when user slides images and check is this like or not same i want in my code...
i made my code in that i come from listing page and when i tap on listing i can see full screen image.. and i Can slide(Scroll) images...
BUTTT when i comes first time on this screen which i code it show me some bugs on screen for 3,4 seconds...after that i can see the image plz help mee and show my error
Future<PhotoDetail> pagedetail() async {
sharedPreferences = await SharedPreferences.getInstance();
Map data = {
"AccessToken": sharedPreferences.getString("AccessToken"),
"CustomerId": sharedPreferences.getInt("CustomerId"),
"ImageId": indexx == null ? widget.images[widget.currentindex].photoId : indexx
};
print(data);
final http.Response response = await http.post(
Constants.CUSTOMER_WEBSERVICE_URL + "/photo/detail",
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
);
var jsonResponse = json.decode(response.body);
if (response.statusCode == 200) {
print("Response status : ${response.statusCode}");
print("Response status : ${response.body}");
isLike = jsonResponse["IsLike"]; //here i saving value when api is run
print("iSLiked Value:" +isLike.toString()); // value is printing here
return PhotoDetail.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load data');
}
}
Future<PhotoDetail> _photoDetail;
#override
void initState() {
_photoDetail = pagedetail();
setState(() {
pagedetail(); //here is my api run when page is load
});
super.initState();
}
void pageChange(int index){ //this method is when i scroll page
setState(() {
pagedetail(); // api is call again and again
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
body: Stack(
children: <Widget>[
Container(
child:
Center(
child: PhotoViewGallery.builder(
itemCount: widget.images.length,
builder: (BuildContext context , int index) {
return PhotoViewGalleryPageOptions(
imageProvider:
NetworkImage(widget.images[index].photoPreviewImagePath),
maxScale: PhotoViewComputedScale.covered * 1.8,
minScale: PhotoViewComputedScale.contained * 1.0,
);
},
pageController: _pageController,
enableRotation: false,
scrollDirection: Axis.horizontal,
onPageChanged: pageChange,
loadingBuilder: (BuildContext context , ImageChunkEvent event){
return Center(child: CircularProgressIndicator(),);
},
)
)
),
Positioned(
bottom: 10,
left: 30,
right: 30,
child: (_photoDetail == null) ? Text("error")
: FutureBuilder<PhotoDetail>(
future: _photoDetail,
// ignore: missing_return
builder: (context , snapshot){
int like = snapshot.data.isLike;
int Slection = snapshot.data.selectedCount;
print("like ?????:" +like.toString());
print("Selection ????? :" + Slection.toString());
if (snapshot.hasData){
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
InkWell(
child: InkWell(
Likes();
Fluttertoast.showToast(
msg: "Liked",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Color(0xfff58634),
textColor: Colors.white,
fontSize: 16.0
);
},
child: Icon(Icons.thumb_up,size: 20,color: like == 1 ? Color(0xfff58634) : Colors.white),
),
),
InkWell(
child: Icon(Icons.comment,size: 20,color: Colors.white),
),
InkWell(
onTap: (){
_onShare();
},
child: Icon(Icons.share,size: 20,color: Colors.white,),
),
InkWell(
onTap: (){},
child: Icon(Icons.photo_album,size: 20,color: Slection == 0 ? Colors.white : Color(0xfff58634)),
),
InkWell(
onTap: (){
setState(() {
viewwholike();
});
},
child: Icon(Icons.card_giftcard,size: 20,color:
Colors.white,),
),
]
);
}
else {
Text("error");
}
},
),
),
],
),
);
}
ERROR::════════ Exception caught by widgets library
═══════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building
FutureBuilder<PhotoDetail>(dirty, state:
_FutureBuilderState<PhotoDetail>#ef81b):
The getter 'isLike' was called on null.
Receiver: null
Tried calling: isLike
The relevant error-causing widget was:
FutureBuilder<PhotoDetail>
file:///C:/Users/Hello%20Devloper/AndroidStudioProjects/
September/PhotoGranth-
App/lib/CampaignSinglePhotos.dart:400:15
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1 _CampaignSinglePhotosState.build.<anonymous closure>
(package:photogranth2020/CampaignSinglePhotos.dart:404:42)
#2 _FutureBuilderState.build
(package:flutter/src/widgets/async.dart:732:55)
#3 StatefulElement.build
(package:flutter/src/widgets/framework.dart:4619:28)
#4 ComponentElement.performRebuild
(package:flutter/src/widgets/framework.dart:4502:15)

Flutter tells you that data is not ready when you test snapshot.data.isLike.
You should test always at the beginning if snapshot.hasData, only after that you can make all you want.
Otherwise, if snapshot.hasData is false, simply return CircularProgressIndicator() .

Related

Flutter FutureBuilder shows data then disappears when calling Rest API (Backend Laravel)

I'm running a flutter application with a laravel backend and I have some issues.
The problem is the FutureBuilder show data then it disappears; Sometimes length==4 then it turns to 0 and shows 'no data' in Scaffold🙄
The same when I refresh the code.
PS: I'm running laravel on localhost and using a real device to test.
Environment: Android Studio, Windows 10, Real device
Laravel project: https://github.com/brakenseddik/blog_api_laravel
Flutter project: https://github.com/brakenseddik/blog_api_flutter
import 'package:http/http.dart' as http;
class Repository {
String _baseUrl = 'http://192.168.1.2:8000/api';
httpGet(String api) async {
return await http.get(_baseUrl + '/' + api);
}
}
here the homepage source code
import 'dart:convert';
import 'package:blog_api/models/post_model.dart';
import 'package:blog_api/services/post_service.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PostService _postService = PostService();
List<PostModel> _list = List<PostModel>();
Future<List<PostModel>> _getPosts() async {
var result = await _postService.getAllPosts();
_list = [];
if (result != null) {
var blogPosts = json.decode(result.body);
blogPosts.forEach((post) {
PostModel model = PostModel();
setState(() {
model.title = post['title'];
model.details = post['details'];
model.imageUrl = post['featured_image_url'];
_list.add(model);
});
});
}
return _list;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Blog App'),
),
body: FutureBuilder(
future: _getPosts(),
builder:
(BuildContext context, AsyncSnapshot<List<PostModel>> snapshot) {
print('length of list ${_list.length}');
_list = snapshot.data;
if (_list.length == 0) {
return Center(
child: Text('No data'),
);
} else if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.network(
snapshot.data[index].imageUrl,
height: 150,
// width: double.maxFinite,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
snapshot.data[index].title,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w700),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
snapshot.data[index].details.substring(0, 25),
style: TextStyle(
fontSize: 16,
),
),
),
],
),
),
);
});
}
},
));
}
}
And the PostService
import 'package:blog_api/repository/repository.dart';
class PostService {
Repository _repository;
PostService() {
_repository = Repository();
}
getAllPosts() async {
return await _repository.httpGet('get-posts');
}
}
I think the issue is that you're calling setState in _getPosts(). This will rebuild everything and never give the response to the FutureBuilder. Just run the code without setState:
blogPosts.forEach((post) {
PostModel model = PostModel();
model.title = post['title'];
model.details = post['details'];
model.imageUrl = post['featured_image_url'];
_list.add(model);
FutureBuilder will initially call its builder with a snapshot that doesn't have any data yet. Once it receives the data, it will call its builder again. Because of this, if (_list.length == 0) will result in a NPE, since _list is null.
I would try changing the order of the if statements:
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.data.length == 0) {
return Center(
child: Text('No data'),
);
} else {
return ListView.builder(
//...
}
future: _getPosts(),
Don't do that. This means every time build is called, you are querying your API again.
Create a variable of type Future<List<PostModel>>, assign _getPosts() to it once and then use that variable with your FutureBuilder.

How to hide a button on click in a flutter application?

normal image
image after entering the mobile number
See in this image when a user clicks on the login button the login button should disappear and a text field will be there followed by a submit button.
I have the text field appearing on click of the login button, however I don't know how to disappear that login button once it is pressed.
import 'dart:async';
import 'dart:ffi';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../widgets/landing_page.dart';
import '../screens/register.dart';
import 'package:google_sign_in/google_sign_in.dart';
import '../widgets/google_sign_in_btn.dart';
import '../widgets/reactive_refresh_indicator.dart';
// Each item on AuthStatus represents quite literally the status of the UI.
// On SOCIAL_AUTH only the GoogleSignInButton will be visible.
enum AuthStatus { SOCIAL_AUTH }
class AuthScreen extends StatefulWidget {
#override
_AuthScreenState createState() => _AuthScreenState();
}
// On _AuthScreenState we start by defining the tag that will be used for our logger, then the default status as SOCIAL_AUTH, which means we need to do Google's sign in and the GoogleSignInButton will be visible and interactive.
class _AuthScreenState extends State<AuthScreen> {
String phoneNo;
String smsCode;
String verificationId;
bool _smsCodeDisabled = true;
bool isThere;
bool isPresent;
bool _canShowButton = false;
final db = Firestore.instance;
Firestore.instance.collection('transcriber_user_registeration').where('mobileno', isEqualTo: phoneNo)
// .snapshots().listen(
// (data) { print("Inside phone number check : $data"); });
// // return phoneNumberCheck(phoneNo);
// QuerySnapshot result =
// await Firestore.instance.collection('transcriber_user_registeration').getDocuments();
// var list = result.documents;
// print("Before data loop");
// list.forEach((data) => print(data));
// print("After data loop");
Future<void> phoneNumberCheck(String phoneNo) async {
print("Start of the function");
//bool registerState = false;
//bool isPresent = false;
Firestore.instance
.collection("transcriber_user_registeration")
.getDocuments()
.then((QuerySnapshot snapshot) {
snapshot.documents.forEach((f) async {
if (isPresent = ('${f.data['mobileno']}' == phoneNo)) {
print(isPresent);
final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
this.verificationId = verId;
};
final PhoneCodeSent smsCodeSent =
(String verId, [int forceCodeResend]) {
this.verificationId = verId;
print("im in sms code dialog");
// smsCodeDialog(context).then((value) {
// print('Signed in');
// });
setState(() {
this._smsCodeDisabled = false;
});
};
final PhoneVerificationCompleted verifySuccess =
(AuthCredential user) {
print("verified");
};
final PhoneVerificationFailed verifyFailed =
(AuthException exception) {
print('${exception.message}');
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: this.phoneNo,
codeAutoRetrievalTimeout: autoRetrieve,
codeSent: smsCodeSent,
timeout: const Duration(seconds: 5),
verificationCompleted: verifySuccess,
verificationFailed: verifyFailed,
);
}
// else {
// _showMessage();
// }
});
});
//print("End of the function $isPresent");
}
Future<void> verifyPhone() async {
// final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
// this.verificationId = verId;
// };
var tmp1 = phoneNo.toString();
print('ref stsmt starts $tmp1');
await phoneNumberCheck(phoneNo);
print("After execution of the function $isPresent");
print('bvnnn');
}
Future<bool> smsCodeDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: Text('Enter sms code'),
content: TextField(onChanged: (value) {
this.smsCode = value;
}),
contentPadding: EdgeInsets.all(10.0),
actions: <Widget>[
new FlatButton(
child: Text('Login'),
onPressed: () async {
//await signIn();
await FirebaseAuth.instance.currentUser().then((user) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => LandingPageApp()));
});
})
],
);
});
}
signIn() {
print("came to sign in page");
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
FirebaseAuth.instance.signInWithCredential(credential).then((user) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LandingPageApp()));
}).catchError((e) {
print(e);
});
}
void hideWidget() {
setState(() {
_canShowButton != _canShowButton;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Flexible(
flex: 1,
child: new Container(
//color: Colors.white,
height: 200.0,
width: 400.0,
decoration: new BoxDecoration(
image: DecorationImage(
image: new AssetImage('assets/images/download.png'),
),
),
),
),
SizedBox(height: 20.0),
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 20.0),
child: TextFormField(
decoration:
InputDecoration(hintText: 'Enter your Phone number'),
keyboardType: TextInputType.phone,
onChanged: (value) {
this.phoneNo = "+91$value";
},
validator: validateMobile),
),
SizedBox(height: 10.0),
Visibility(
visible: _canShowButton,
child: RaisedButton(
child: Text('Login'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
onPressed: () async {
bool _canShowButton = true;
await verifyPhone();
setState(() {
_canShowButton = !_canShowButton;
});
hideWidget();
//_number();
},
),
),
_smsCodeDisabled
? SizedBox(height: 10.0)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 16.0),
child: TextFormField(
decoration:
InputDecoration(hintText: 'Enter OTP'),
keyboardType: TextInputType.number,
onChanged: (value) {
this.smsCode = value;
},
validator: validateOtp),
),
SizedBox(height: 10.0),
RaisedButton(
onPressed: () async {
await signIn();
FirebaseAuth.instance.currentUser().then((user) {
print(["user", user]);
if (user != null) {
print(user.uid);
// Navigator.of(context).pop();
//// Navigator.of(context).pushReplacementNamed('/homePage');
// Navigator.of(context).push(
// MaterialPageRoute<Null>(
// builder: (BuildContext context) {
// return new LandingPageApp();
// }));
} else {
print("user is null");
Navigator.of(context).pop();
signIn();
}
});
},
child: Text('Submit'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
),
],
),
SizedBox(
height: 20.0,
),
Column(
children: <Widget>[
Row(children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
vertical: 16.0, horizontal: 20.0),
child: Text(
'Not a Registered User?',
style: TextStyle(
fontSize: 16,
),
),
),
MaterialButton(
child: Text(
'Register',
style: TextStyle(
color: Colors.black,
),
),
//color: Colors.blue,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Register()));
},
),
]),
],
),
],
),
));
}
}
String validateMobile(String value) {
//Indian Mobile numbers are of 10 digits only.
if (value.length != 10)
return 'Mobile number must be of 10 digits';
else
return null;
}
String validateOtp(String value) {
//Otp needs to be of 6 digits
if (value.length != 6)
return 'OTP must be of 6 digits';
else
return null;
}
Please try this
bool _canShowButton = true;
void hideWidget() {
setState(() {
_canShowButton = !_canShowButton;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Screen'),
),
body: Container(
padding: const EdgeInsets.all(8),
child: Column(
children: <Widget>[
Flexible(
flex: 1,
child: Container(
//color: Colors.white,
height: 200.0,
width: 400.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('https://picsum.photos/250?image=10'),
),
),
),
),
SizedBox(height: 20.0),
///if the show button is false
!_canShowButton
? const SizedBox.shrink()
: RaisedButton(
child: Text('Login'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
onPressed: () {
hideWidget();
//_number();
},
),
],
)),
);
}
In your build method, you already have a boolean _smsCodeDisabled to identify whether the otp field need to show or not. You can use the same boolean to hide the login field and button. The logic should be something like:
#override
Widget build(BuildContext context) {
return _smsCodeDisabled ? _getLoginWidget() : _getOtpWidget();
}
Widget _getLoginWidget() {
// Return Login field and button
}
Widget _getOtpWidget() {
// Return otp field and button
}
Or If you need to only hide the login button, use the following logic:
#override
Widget build(BuildContext context) {
return Scaffold(
// other code
// Login button logic
_smsCodeDisabled ? RaisedButton(...) : Container();
);
}

Flutter: How to put button on each image like (x) to cancel selected image

I am using multi_image_picker 4.6.1 in my application but I faced little problem. How to organize images on specific place on the page and put cancel button on each selected image so user can cancel or remove selected image one by one like in picture here. Thanks in advance
here is the code i am using
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error = 'No Error Dectected';
#override
void initState() {
super.initState();
}
Widget buildGridView() {
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return AssetThumb(
asset: asset,
width: 300,
height: 300,
);
}),
);
}
Future<void> loadAssets() async {
List<Asset> resultList = List<Asset>();
String error = 'No Error Dectected';
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
enableCamera: true,
selectedAssets: images,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Example App",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
_error = error;
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
Another way to fix using Stack
Stack(
children: <Widget>[
AssetThumb(
asset: asset,
width: 300,
height: 300,
),
Positioned(
right: -2,
top: -9,
child: IconButton(
icon: Icon(
Icons.cancel,
color: Colors.black.withOpacity(0.5),
size: 18,
),
onPressed: () => setState(() {
images.removeAt(index);
})))
],
);
You can try using Stack https://www.youtube.com/watch?v=liEGSeD3Zt8&vl=en
return Stack(
children: <Widget>[
AssetThumb(
asset: asset,
width: 300,
height: 300,
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: (){
print('delete image from List');
setState((){
print('set new state of images');
})
},
child: Icon(
Icons.delete,
),
),
),
],
);

How can I select and upload Multiple images in Flutter

I tried to find how to select and upload multiple images in flutter but most of the plugins are not working or I did not understand them well. I found little application but it selects and upload only one picture. How to change this code that user can select and upload multiple pictures or is there any other alternatives. Please write in details, i am freshman in coding. Thanks in advance.
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:toast/toast.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image Upload Demo',
theme: ThemeData(primarySwatch: Colors.pink),
home: ImageInput());
}
}
class ImageInput extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ImageInput();
}
}
class _ImageInput extends State<ImageInput> {
// To store the file provided by the image_picker
File _imageFile;
// To track the file uploading state
bool _isUploading = false;
String baseUrl = 'http://YOUR_IPV4_ADDRESS/flutterdemoapi/api.php';
void _getImage(BuildContext context, ImageSource source) async {
File image = await ImagePicker.pickImage(source: source);
setState(() {
_imageFile = image;
});
// Closes the bottom sheet
Navigator.pop(context);
}
Future<Map<String, dynamic>> _uploadImage(File image) async {
setState(() {
_isUploading = true;
});
// Find the mime type of the selected file by looking at the header bytes of the file
final mimeTypeData =
lookupMimeType(image.path, headerBytes: [0xFF, 0xD8]).split('/');
// Intilize the multipart request
final imageUploadRequest =
http.MultipartRequest('POST', Uri.parse(baseUrl));
// Attach the file in the request
final file = await http.MultipartFile.fromPath('image', image.path,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
// Explicitly pass the extension of the image with request body
// Since image_picker has some bugs due which it mixes up
// image extension with file name like this filenamejpge
// Which creates some problem at the server side to manage
// or verify the file extension
imageUploadRequest.fields['ext'] = mimeTypeData[1];
imageUploadRequest.files.add(file);
try {
final streamedResponse = await imageUploadRequest.send();
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode != 200) {
return null;
}
final Map<String, dynamic> responseData = json.decode(response.body);
_resetState();
return responseData;
} catch (e) {
print(e);
return null;
}
}
void _startUploading() async {
final Map<String, dynamic> response = await _uploadImage(_imageFile);
print(response);
// Check if any error occured
if (response == null || response.containsKey("error")) {
Toast.show("Image Upload Failed!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
} else {
Toast.show("Image Uploaded Successfully!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
}
}
void _resetState() {
setState(() {
_isUploading = false;
_imageFile = null;
});
}
void _openImagePickerModal(BuildContext context) {
final flatButtonColor = Theme.of(context).primaryColor;
print('Image Picker Modal Called');
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 150.0,
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Text(
'Pick an image',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(
height: 10.0,
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Camera'),
onPressed: () {
_getImage(context, ImageSource.camera);
},
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Gallery'),
onPressed: () {
_getImage(context, ImageSource.gallery);
},
),
],
),
);
});
}
Widget _buildUploadBtn() {
Widget btnWidget = Container();
if (_isUploading) {
// File is being uploaded then show a progress indicator
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: CircularProgressIndicator());
} else if (!_isUploading && _imageFile != null) {
// If image is picked by the user then show a upload btn
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
child: Text('Upload'),
onPressed: () {
_startUploading();
},
color: Colors.pinkAccent,
textColor: Colors.white,
),
);
}
return btnWidget;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Upload Demo'),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 40.0, left: 10.0, right: 10.0),
child: OutlineButton(
onPressed: () => _openImagePickerModal(context),
borderSide:
BorderSide(color: Theme.of(context).accentColor, width: 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.camera_alt),
SizedBox(
width: 5.0,
),
Text('Add Image'),
],
),
),
),
_imageFile == null
? Text('Please pick an image')
: Image.file(
_imageFile,
fit: BoxFit.cover,
height: 300.0,
alignment: Alignment.topCenter,
width: MediaQuery.of(context).size.width,
),
_buildUploadBtn(),
],
),
);
}
}
I used that package:
dependencies:
multi_image_picker: ^4.6.7
Ex:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error;
#override
void initState() {
super.initState();
}
Widget buildGridView() {
if (images != null)
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return AssetThumb(
asset: asset,
width: 300,
height: 300,
);
}),
);
else
return Container(color: Colors.white);
}
Future<void> loadAssets() async {
setState(() {
images = List<Asset>();
});
List<Asset> resultList;
String error;
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
);
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
if (error == null) _error = 'No Error Dectected';
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
Add dependecy of image_picker:
image_picker: ^0.8.4+3
Then make a method for selectImages():
final ImagePicker imagePicker = ImagePicker();
List<XFile>? imageFileList = [];
void selectImages() async {
final List<XFile>? selectedImages = await
imagePicker.pickMultiImage();
if (selectedImages!.isNotEmpty) {
imageFileList!.addAll(selectedImages);
}
print("Image List Length:" + imageFileList!.length.toString());
setState((){});
}
Create a builder for showing selected Images:
return Scaffold(
appBar: AppBar(
title: Text('Multiple Images'),
),
body: SafeArea(
child: Column(
children: [
ElevatedButton(
onPressed: () {
selectImages();
},
child: Text('Select Images'),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
itemCount: imageFileList!.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(imageFileList![index].path),
fit: BoxFit.cover,);
}),
),
),
],
),
));
Complete Source code available in github link...
https://github.com/NishaJain24/multi_image_picker

Flutter ListView.builder - How to Jump to Certain Index Programmatically

i have a screen that build using MaterialApp, DefaultTabController, Scaffold and TabBarView.
in this screen, i have body content that retreive a list of element from sqllite using StreamBuilder. i get exact 100 elements ("finite list") to be shown using ListView.
my question, using ListView.builder, How we can jump to certain index when this screen opened ?
my main screen:
...
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner : false,
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: Pigment.fromString(UIData.primaryColor),
elevation: 0,
centerTitle: true,
title: Text(translations.text("quran").toUpperCase()),
bottom: TabBar(
tabs: [
Text("Tab1"),
Text("Tab2"),
Text("Tab3")
],
),
leading: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: InkWell(
child: SizedBox(child: Image.asset("assets/images/home.png"), height: 10, width: 1,),
onTap: () => Navigator.of(context).pop(),
)
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Testing Index Jump',
child: Text("GO"),
),
body:
TabBarView(
children: [
Stack(
children: <Widget>[
MyDraggableScrollBar.create(
scrollController: controller,
context: context,
heightScrollThumb: 25,
child: ListView(
controller: controller,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(30, 15, 30, 8),
child: Container(
alignment: Alignment.center,
height: 30,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: TextField(
style: TextStyle(color: Colors.green),
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(5),
border: InputBorder.none,
filled: true,
hintStyle: new TextStyle(color: Colors.green, fontSize: 14),
prefixIcon: Icon(FontAwesomeIcons.search,color: Colors.green,size: 17,),
hintText: translations.text("search-quran"),
fillColor: Colors.grey[300],
prefixStyle: TextStyle(color: Colors.green)
),
onChanged: (val) => quranBloc.searchSurah(val),
),
)
)
),
//surah list
streamBuilderQuranSurah(context)
],
)
) // MyDraggableScrollBar
],
),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
)
)));
}
Widget streamBuilderQuranSurah(BuildContext ctx){
return StreamBuilder(
stream: quranBloc.chapterStream ,
builder: (BuildContext context, AsyncSnapshot<ChaptersModel> snapshot){
if(snapshot.hasData){
return ListView.builder(
controller: controller,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount:(snapshot.data.chapters?.length ?? 0),
itemBuilder: (BuildContext context, int index) {
var chapter =
snapshot.data.chapters?.elementAt(index);
return chapterDataCell(chapter);
},
);
}
else{
return SurahItemShimmer();
}
},
);
}
...
class MyDraggableScrollBar.dart :
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
class MyDraggableScrollBar {
static Widget create({
#required BuildContext context,
#required ScrollController scrollController,
#required double heightScrollThumb,
#required Widget child,
}) {
return DraggableScrollbar(
alwaysVisibleScrollThumb: true,
scrollbarTimeToFade: Duration(seconds: 3),
controller: scrollController,
heightScrollThumb: heightScrollThumb,
backgroundColor: Colors.green,
scrollThumbBuilder: (
Color backgroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
Text labelText,
BoxConstraints labelConstraints,
}) {
return InkWell(
onTap: () {},
child: Container(
height: height,
width: 7,
color: backgroundColor,
),
);
},
child: child,
);
}
}
i have tried find other solutions but seems not working, for example indexed_list_view that only support infinite list
and it seems flutter still not have feature for this, see this issue
Any Idea ?
You can use https://pub.dev/packages/scrollable_positioned_list. You can pass the initial index to the widget.
ScrollablePositionedList.builder(
initialScrollIndex: 12, //you can pass the desired index here//
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
General Solution:
To store anything which can be represented as a number/string/list of strings, Flutter provides a powerful easy-to-use plugin which stores the values needed to be stored along with a key. So the next time you need you'll need to retrieve or even update that value all that you'll need is that key.
To get started, add the shared_preferences plugin to the pubspec.yaml file,
dependencies:
flutter:
sdk: flutter
shared_preferences: "<newest version>"
Run flutter pub get from the terminal or if your using IntelliJ just click on Packages get(You'll find it somewhere around the top-right corner of your screen while viewing the pubspec.yaml file)
Once the above command is successfully executed, import the below file in your main.dart or concerned file.
import 'package:shared_preferences/shared_preferences.dart';
Now just attach a ScrollController to your ListView.builder() widget and make sure that the final/last offset is stored along with a specific key using shared_preferences whenever the user leaves the app in any way and is set when the initState of your concerned widget is called.
In order to know to detect changes in the state of our app and to act with accordance to it, we'll be inheriting WidgetsBindingObserver to our class.
Steps to follow:
Extend the WidgetsBindingObserver class along with the State class of your StatefulWidget.
Define a async function resumeController() as a function member of the above class.
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
Declare two variables one to store and pass the scrollcontroller and the other to store and use the instance of SharedPreferences.
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Call resumeController() and pass your class to the addObserver method of the instance object in WidgetsBinding class.
resumeController();
WidgetsBinding.instance.addObserver(this);
Simply paste this code in the class definition (outside other member functions)
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
Pass the ScrollController() to the concerned Scrollable.
Working Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{
//[...]
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
}
#override
void initState() {
resumeController();
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Smart Scroll View"),
),
body: ListView.builder(
itemCount: 50,
controller: _scrollController,
itemBuilder: (c,i)=>
Padding(
padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),
child: Text((i+1).toString()),
),
),
),
);
}
}
Solution without knowing the size of your widgets
the Solution I found without knowing the size of your widget is displaying a reverse 'sublist' from the index to the end, then scroll to the top of your 'sublist' and reset the entire list. As it is a reverse list the item will be add at the top of the list and you will stay at your position (the index).
the problem is that you can't use a listView.builder because you will need to change the size of the list
example
class _ListViewIndexState extends State<ListViewIndex> {
ScrollController _scrollController;
List<Widget> _displayedList;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_displayedList = widget.items.sublist(0, widget.items.length - widget.index);
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
//here the sublist is already build
completeList();
});
}
}
completeList() {
//to go to the last item(in first position)
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
//reset the list to the full list
setState(() {
_displayedList = widget.items;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _scrollController,
reverse: true,
children: _displayedList,
),
]
);
}
}
The https://pub.dev/packages/indexed_list_view package could maybe help you out for this. Use something like this:
IndexedListView.builder(
controller: indexScrollController,
itemBuilder: itemBuilder
);
indexScrollController.jumpToIndex(10000);
I'll present another approach, which supports list lazy loading unlike #Shinbly 's method, and also support tiles in list to resize without recalculating the correct offset of the ListView nor saving any persistent information like "#Nephew of Stackoverflow" does.
The essential key to this approach is to utilize CustomScrollView, the CustomScrollView.center property.
Here's an example based on the example code from Flutter document (widgets.CustomScrollView.2):
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> top = [];
List<int> bottom = [0];
List<int> test = List.generate(10, (i) => -5 + i);
bool positionSwitcher = true;
#override
Widget build(BuildContext context) {
positionSwitcher = !positionSwitcher;
final jumpIndex = positionSwitcher ? 1 : 9;
Key centerKey = ValueKey('bottom-sliver-list');
return Scaffold(
appBar: AppBar(
title: const Text('Press Jump!! to jump between'),
leading: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
top.add(-top.length - 1);
bottom.add(bottom.length);
});
},
),
),
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RaisedButton(
child: Text('Jump!!'),
onPressed: () => setState(() {}),
),
Text(positionSwitcher ? 'At top' : 'At bottom'),
],
),
Expanded(
child: CustomScrollView(
center: centerKey,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = jumpIndex - 1 - i;
return Container(
alignment: Alignment.center,
color: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: jumpIndex,
),
),
SliverList(
key: centerKey,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = i + jumpIndex;
return Container(
alignment: Alignment.center,
color: i == 0
? Colors.red
: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: test.length - jumpIndex,
),
),
],
),
)
],
),
);
}
}
Explanation:
We use single list as data source for both SliverList
During each rebuild, we use center key to reposition the second SliverList inside ViewPort
Carefully manage the conversion from SliverList index to data source list index
Notice how the scroll view build the first SliverList by passing an index starting from bottom of this SliverList (i.e. index 0 suggests last item in the first list sliver)
Give the CustomeScrollView a proper key to decide whether to "re-position" or not
Working Example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ScrollToIndexDemo extends StatefulWidget {
const ScrollToIndexDemo({Key? key}) : super(key: key);
#override
_ScrollToIndexDemoState createState() => _ScrollToIndexDemoState();
}
class _ScrollToIndexDemoState extends State<ScrollToIndexDemo> {
late AutoScrollController controller = AutoScrollController();
var rng = Random();
ValueNotifier<int> scrollIndex = ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ValueListenableBuilder(
valueListenable: scrollIndex,
builder: (context, index, child) {
return Text('Scroll Demo - $index');
},
),
),
body: ListView.builder(
itemCount: 100,
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(8),
child: AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
highlightColor: Colors.black.withOpacity(0.1),
child: Container(
padding: EdgeInsets.all(10),
alignment: Alignment.center,
color: Colors.grey[300],
height: 100,
child: Text(
'index: $index',
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
scrollIndex.value = rng.nextInt(100);
await controller.scrollToIndex(scrollIndex.value, preferPosition: AutoScrollPosition.begin);
},
tooltip: 'Increment',
child: Center(
child: Text(
'Next',
textAlign: TextAlign.center,
),
),
),
);
}
}
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.builder(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);

Resources