How to get the number of data that are retrieving through response.body? - laravel

How can I get the number of data that are retrieving through response.body?
I tried many methods including below one to get that number. But non of them worked out pretty well.
var convertJsonToData = json.decode(response.body);
data = convertJsonToData;
data.forEach((element) => touristCount++);
The variable touristCount is declared and initialized to 0 at the beginning of this dart file.
Here are my codes.
AdminMenu.dart
String apiurl = "http://10.0.2.2:8000/api/retrieveTourists/";
List data;
var touristCount = 0;
class _AdminMenuState extends State<AdminMenu> {
#override
// var touristCount = 2;
// _AdminMenuState({this.touristCount});
void initState() {
super.initState();
this.getTouristCount(context);
}
void getTouristCount(BuildContext context) async {
var response = await http
.get(Uri.encodeFull(apiurl), headers: {"Accept": "application/json"});
if (this.mounted) {
setState(() {
var convertJsonToData = json.decode(response.body);
data = convertJsonToData;
data.forEach((element) => touristCount++);
});
}
}
..................
..................
....................
AuthController.php
public function retrieveTourists(){
$user = tourists::all();
return response()->json($user);
}
api.php
Route::get('/retrieveTourists','Api\AuthController#retrieveTourists');
All I want is to display the count at "Text" here.
Widget buttonSection1 = Container(
padding: const EdgeInsets.only(top: 30),
decoration: BoxDecoration(
border: Border.all(width: 5, color: Colors.black38),
borderRadius: const BorderRadius.all(const Radius.circular(8)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(color, Icons.people, 'Guides'),
Text(touristCount.toString()), <----------------------------------- Display the count???
],
),
);
Can someone help me with this? :)

If your incoming data is an array, you can use the length property to get the number of data, like this:
if (this.mounted) {
setState(() {
var convertJsonToData = json.decode(response.body);
touristCount = convertJsonToData.length
});
}

I found a way to do that! We can get this thing done by passing the touristCount variable as a parameter to the buttonSection1 widget
Widget buttonSection1(int num_guides){
return Container(
padding: const EdgeInsets.only(top: 30),
decoration: BoxDecoration(
border: Border.all(width: 5, color: Colors.black38),
borderRadius: const BorderRadius.all(const Radius.circular(8)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(color, Icons.people, 'Guides'),
Text(num_guides.toString()),
],
),
);
}
Calling the widget in build widget
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
leading: Icon(Icons.menu),
title: Text("Admin Menu"),
backgroundColor: Colors.indigo,
actions: <Widget>[
IconButton(icon: Icon(Icons.person)),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buttonSection1(touristCount),
.....................
.....................

Related

Stream Builder causing infinite loop flutter

I have stored data on firestore, but when I retrieve it.
It causes infinite loop. The number of items in my list keeps on increasing.
class PotHolesList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final potholesData = Provider.of<PotHoles>(context);
Future<File> _createFileFromString(String encodedStr) async {
Uint8List bytes = base64Decode(encodedStr);
String dir = (await getApplicationDocumentsDirectory()).path;
File file = File(
"$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + ".png");
await file.writeAsBytes(bytes);
return file;
}
return StreamBuilder(
stream: _firestore.collection('potholes').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {...}
final potholes = snapshot.data.docs;
for (var pt in potholes) {
final id = pt['id'];
final address = pt['address'];
final latitude = pt['latitude'];
final longitude = pt['longitude'];
var image;
var P;
_createFileFromString(pt['image']).then((value) {
image = value;
P = PotHole(
id: id,
currentPosition: Position.fromMap(
{'latitude': latitude, 'longitude': longitude}),
address: address,
image: image,
);
potholesData.addPothole(P);
});
print(potholesData.items.length);
}
return ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: potholesData.items.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: potholesData.items[i],
child: PotHoleItem(),
),
);
},
);
}
}
I have tried converting the image base64 string from firestore to a file type in flutter, since it is a future, I have to wait, then do the rest of the adding.
This is the updated code, but it still gives infinite loop, the list items length keeps on increasing indefinitely.
class PotHolesList extends StatelessWidget {
final _stream = _firestore.collection('potholes').snapshots();
Future<File> _createFileFromString(String encodedStr) async {
Uint8List bytes = base64Decode(encodedStr);
String dir = (await getApplicationDocumentsDirectory()).path;
File file = File(
"$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + ".png");
await file.writeAsBytes(bytes);
return file;
}
#override
Widget build(BuildContext context) {
final potholesData = Provider.of<PotHoles>(context);
return StreamBuilder(
stream: _stream,
// ignore: missing_return
builder: (context, snapshot) {
if (!snapshot.hasData) {...}
final potholes = snapshot.data.docs;
for (var pt in potholes) {
final id = pt['id'];
final address = pt['address'];
final latitude = pt['latitude'];
final longitude = pt['longitude'];
var image;
var P;
_createFileFromString(pt['image']).then((value) {
image = value;
P = PotHole(
id: id,
currentPosition: Position.fromMap(
{'latitude': latitude, 'longitude': longitude}),
address: address,
image: image,
);
potholesData.addPothole(P);
});
print(potholesData.items.length);
}
return ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: potholesData.items.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: potholesData.items[i],
child: PotHoleItem(),
),
);
},
);
}
}
This is what PotHoleItem code is:
class PotHoleItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
final pothole = Provider.of<PotHole>(context, listen: false);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 14.0),
child: Container(
decoration: BoxDecoration(
color: Color(0xFFd8e2dc),
borderRadius: BorderRadius.circular(15.0)),
child: ListTile(
trailing: Icon(pothole.isFixed ? Icons.check : Icons.clear),
title: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 5.0),
// height: double.infinity,
alignment: Alignment.center,
child: Image.file(
pothole.Image,
fit: BoxFit.contain,
width: 65,
height: 65,
),
),
SizedBox(
width: 10.0,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"LAT: ${pothole.Latitude}, LONG: ${pothole.Longitude}",
style: TextStyle(
fontSize: 14.0, fontWeight: FontWeight.bold),
),
SizedBox(height: 10.0),
SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: Text(
"${pothole.Address}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.0, fontWeight: FontWeight.normal),
),
),
],
),
),
],
),
onTap: () {
Navigator.of(context)
.pushNamed(PotHoleDetailScreen.routeName, arguments: pothole);
},
),
),
);
}
}
Infinite list
List size should be 1
You are creating the future in the call to FutureBuilder for the future: parameter. Don't do that. The docs for FutureBuilder say:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.
I have a nice 10-minute screencast that illustrates this point: https://www.youtube.com/watch?v=sqE-J8YJnpg.

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();
);
}

How to upload image in Flutter using Image Picker

I tried to save image in image upload form using image picker, I used "OnSaved" but there are error said "The named Parameter OnSaved isnt defined. How can I solve this?
This is the code that i use :
onSaved: (val) => _image = val,
This is the complete code:
import 'dart:io';
import 'package:image_picker/image_picker.dart';
class NewPostScreen extends StatefulWidget {
#override
_NewPostScreen createState() => _NewPostScreen();
}
class _NewPostScreen extends State<NewPostScreen> {
File _image;
Future getImageFromGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
});
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
child: Scaffold(
key: _scaffoldkey,
appBar: AppBar(
backgroundColor: Colors.blue,
title: new Text("Create Post"),
),
resizeToAvoidBottomPadding: false,
body: SingleChildScrollView(
padding: new EdgeInsets.all(8.0),
child: Form(
key: _formkey,
child: Column(
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(
/*hintText: "Tujuan",*/
labelText: "Tujuan"),
onSaved: (String val) => destination = val,
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Center(
child: _image == null
? Text('Upload Foto')
: Image.file(_image),
onSaved: (val) => _image = val,
),
),
SizedBox(
width: 100.0,
height: 100.0,
child: RaisedButton(
onPressed: getImageFromGallery,
child: Icon(Icons.add_a_photo),
)),
there is no onSaved property in the Center widget. You can use a button instead and utilize the onPressed property or any other approach that you prefer.

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,
);

Flutter: "(path): must not be null" on conditional file checks

I am trying to have a background to my application that either the user has chosen previously, or to use a default image if no file is found.
The code is very simple:
decoration: BoxDecoration(
color: Colors.blueAccent,
image: DecorationImage(
image: File(customImageFile).existsSync() ? FileImage(File(customImageFile)) : backgroundImage,
fit: BoxFit.fill,
),
),
The application crashes on the conditional line, saying that (path): Must not be null. I have tried many ways to get around this, but what I am specifically saying there is "if the file path is null, don't use it. Use this image I made earlier for you". But then it gets upset anyway.
Is there another way I can achieve the intended result here?
Edit:
As requested by #Vikas, more complete sample of code involved:
ImageProvider backgroundImage;
String themeLoader = 'blueTheme';
String customImageFile;
// Declaration
void initState() {
_loadThemeSettings();
super.initState();
}
// Initialisation
_loadThemeSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
themeLoader = prefs.getString('themeColour' ?? 'blueTheme');
customImageFile = prefs.getString('customImageFile' ?? 'empty')
if (customImageFile != null) {
if(File(customImageFile).existsSync()) {
prefs.setString('customImageFile', customImageFile);
} else {
print('Path not valid');
}
} else {
print('customImageFile is NULL');
}
if (themeLoader != null) {
switch (themeLoader) {
case 'blueTheme':
{
colorBloc.changeColor('blueTheme');
setState(() {
backgroundImage = ExactAssetImage('lib/images/bg_blue_new.jpg');
buttonTheme = AppState.blueTheme;
}
}
break;
} else {
print('Theme loader was NULL');
}
}
}
}
// Loading previously selected themes at run time. There are several colours here in the case statement, but they are all identical and you get the idea.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: Scaffold(
body: SafeArea(
child: BlocProvider<ColorBloc>(
bloc: colorBloc,
child: Container(
child: Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.blueAccent,
image: DecorationImage(
image: (customImageFile != null && File(customImageFile).existsSync()) ? FileImage(File(customImageFile)) : backgroundImage,
fit: BoxFit.fill,
),
),
// that's where it sits, everything else works fine. The rest of the build method is not relevant to this issue, and is working, but here it is anyway:
child: SafeArea(
child: Stack(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
themesVisible == true
? SizedBox()
: buttonRow(),
SizedBox(
height: 20,
),
],
),
Padding(
padding: EdgeInsets.all(15),
child: topMenuRow(),
),
],
),
),
),
BlocProvider<CustomToggleBloc>(
bloc: customToggleBloc,
child: Center(
child: Container(
child: themesVisible == true ? singlePlayerThemes(singlePlayerCallbacks: callBacks) : scoreText(),
),
),
),//bloc
],
),
),
),
),
),
);
}

Resources