Related
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.
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() .
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.
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,
);
I am new to Flutter. I try to load network images using image.network widget. it's working fine but sometimes it takes time to load. I added tap listener to image.network during tap I need to check image is fully loaded or not based on the result I need to redirect the page. how to check image is loaded or not?
Code:
new Image.network('http://via.placeholder.com/350x150')
Any help will be appreciated, thank you in advance
You may use the loadingBuilder which is inbuilt feature from flutter for Image.Network
I did it as below:
Image.network(imageURL,fit: BoxFit.cover,
loadingBuilder:(BuildContext context, Widget child,ImageChunkEvent loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ?
loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
for this kind of issues it's good to use the cached_network_image
so you can provide a placeholder when the image is loading and an error widget in case a resource fails to load
String url = "http://via.placeholder.com/350x150";
CachedNetworkImage(
imageUrl: url,
placeholder: (context,url) => CircularProgressIndicator(),
errorWidget: (context,url,error) => new Icon(Icons.error),
),
for ones who do not need to cache the image can use meet_network_image package,
The package basic usage :
MeetNetworkImage(
imageUrl:
"https://random.dog/3f62f2c1-e0cb-4077-8cd9-1ca76bfe98d5.jpg",
loadingBuilder: (context) => Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, e) => Center(
child: Text('Error appear!'),
),
)
In addition, you can do that by yourself with using a FutureBuilder,
We need to get data with http call that way, we need to import http before import you also need to add pubspec.yaml and run the command flutter packages get
import 'package:http/http.dart' as http;
FutureBuilder(
// Paste your image URL inside the htt.get method as a parameter
future: http.get(
"https://random.dog/3f62f2c1-e0cb-4077-8cd9-1ca76bfe98d5.jpg"),
builder: (BuildContext context, AsyncSnapshot<http.Response> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return CircularProgressIndicator();
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
// when we get the data from the http call, we give the bodyBytes to Image.memory for showing the image
return Image.memory(snapshot.data.bodyBytes);
}
return null; // unreachable
},
);
This way it will start loading, then it will show the loading of the image loading and then the image. Best option if you don't want to use external libs.
Image.network(
imgUrl,
height: 300,
fit: BoxFit.contain,
frameBuilder: (_, image, loadingBuilder, __) {
if (loadingBuilder == null) {
return const SizedBox(
height: 300,
child: Center(child: CircularProgressIndicator()),
);
}
return image;
},
loadingBuilder: (BuildContext context, Widget image, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return image;
return SizedBox(
height: 300,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
errorBuilder: (_, __, ___) => Image.asset(
AppImages.withoutPicture,
height: 300,
fit: BoxFit.fitHeight,
),
)
thank you for your comment thats help to resolve the situation that how to know if the image is loaded or not hope that help
I use a StatefulWidget
need a editing depend on your AffichScreen
situation :
-i have an url that i enter
-if url is correct affich the image if not affich an icon
-if empty affich a Text()
-precacheImage check if the url is correct if not give an error and change _loadingimage(bool) to false to affich the icon eror
-i use a NetworkImage to check with precacheImage and before affich use a Image.network
bool _loadingimage;
ImageProvider _image;
Image _imagescreen;
#override
void initState() {
_loadingimage = true;
_imageUrlfocusNode.addListener(_updateImageUrl);
super.initState();
}
#override
void dispose() {
_imageUrlfocusNode.removeListener(_updateImageUrl);
_quantityfocusNode.dispose();
_imageUrlConroller.dispose();
_imageUrlfocusNode.dispose();
super.dispose();
}
void _updateImageUrl() {
setState(() {
_image = NetworkImage(_imageUrlConroller.text);
});
if (!_imageUrlfocusNode.hasFocus) {
if (_imageUrlConroller.text.isNotEmpty) {
setState(() {
loadimage();
});
}
}
}
void loadimage() {
_loadingimage = true;
precacheImage(_image, context, onError: (e, stackTrace) {
// log.fine('Image ${widget.url} failed to load with error $e.');
print('error $e');
setState(() {
_loadingimage = false;
print(_loadingimage);
});
});
if (_loadingimage == true) {
_imagescreen = Image.network(
_imageUrlConroller.text,
fit: BoxFit.fill,
);
}
}
Container(
width: 100,
height: 100,
margin: EdgeInsets.only(top: 13, right: 11),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child:_imageUrlConroller.text.isEmpty
? Text('enter an url')
: !_loadingimage
? Container(
child: Icon(Icons.add_a_photo),
)
: Container(
child: _imagescreen,
),
),