Scroll synchronisation for multiple scrollable widgets - recursion

Scroll synchronisation for multiple scrollable widgets:
I want to scroll second list if scroll first list and scroll first list if scroll second list.It is going to Recursive can anyone help for this, thanks in advance.
import 'package:flutter/cupertino.dart';
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController firstScroll = ScrollController();
ScrollController secondScrollController = ScrollController();
#override
void initState() {
super.initState();
firstScroll.addListener(() {
//THIS IS called when scroll is triggered,
secondScrollController
.jumpTo(firstScroll.offset); // THIS will sync the scroll;
});
secondScrollController.addListener(() {
//THIS IS called when scroll is triggered,
firstScroll
.jumpTo(secondScrollController.offset); // THIS will sync the scroll;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
SingleChildScrollView(
// this is the first scroll
scrollDirection: Axis.horizontal,
controller: firstScroll, // THIS IS THE FIRST SCROLL CONTROLLER
child: Container(
//TODO: add your content here here
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: secondScrollController,
// HERE YOU SET THE SECOND CONTROLLER
child: Container(
//TODO: add your content here
),
)
],
),
);
}
}

That's because every time you call jumpTo method it call the first one, and the first one call the second one and you will have an infinity loop.
The solution is that you create your own ScrollController with it owns method to jump to another position without notification.
This is the custom scroll controller that you can create:
class CustomScrollController extends ScrollController {
CustomScrollController({
double initialScrollOffset = 0.0,
keepScrollOffset = true,
debugLabel,
}) : super(
initialScrollOffset: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
debugLabel: debugLabel);
#override
_UnboundedScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return _UnboundedScrollPosition(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialScrollOffset,
);
}
void jumpToWithoutGoingIdleAndKeepingBallistic(double value) {
assert(positions.isNotEmpty, 'ScrollController not attached.');
for (_UnboundedScrollPosition position
in new List<ScrollPosition>.from(positions))
position.jumpToWithoutGoingIdleAndKeepingBallistic(value);
}
}
class _UnboundedScrollPosition extends ScrollPositionWithSingleContext {
_UnboundedScrollPosition({
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
double initialPixels,
}) : super(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialPixels,
);
/// There is a feedback-loop between aboveController and belowController. When one of them is
/// being used, it controls the other. However if they get out of sync, for timing reasons,
/// the controlled one with try to control the other, and the jump will stop the real controller.
/// For this reason, we can't let one stop the other (idle and ballistics) in this situation.
void jumpToWithoutGoingIdleAndKeepingBallistic(double value) {
if (pixels != value) {
forcePixels(value);
}
}
}
And just call to jumpToWithoutGoingIdleAndKeepingBallistic instead of jumpTo .
A working sample here:
https://gist.github.com/diegoveloper/75e55ca2e4cee03bff41a26254d6fcf6
Result

Related

Navigation Bar Flutter

I'm trying to have a navigation bar that can navigate into 3 pages (Home, Profile, and Favorites). I follow a course online, somehow it didn't list the file that normally other people do to navigate (example: List _pages = [Home(),Profile()]).
I try to create the list with all the context inside however I got an error on the 'widget' saying that it can't be access in a initalizer.
Since the code is connected to the Firebase, removing 'user widget.user' will lose all the data of the current user such as the their favorites food.
Is there a way to change the body in the Home.dart so that it can navigate to multiple pages since the code below is only ideal for 2 pages (ItemList & ProfileScreen) to navigates.
Home.Dart
class HomeScreen extends StatefulWidget {
final Function logOut;
final User user;
HomeScreen({#required this.logOut, #required this.user});
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int currentItem = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Order Je!',
style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
),
centerTitle: true,
backgroundColor: MainColors.whiteColor,
elevation: 0,
),
body: currentItem == 1
? ProfileScreen(
logout: () {
widget.logOut();
},
)
: ItemList(user: widget.user),
bottomNavigationBar: CustomNavBar(
currentSelectedItem: (value) {
setState(() {
currentItem = value;
});
},
),
);
}
}
Custom Nav
class CustomNavBar extends StatefulWidget {
final Function(int) currentSelectedItem;
CustomNavBar({#required this.currentSelectedItem});
#override
_CustomNavBarState createState() => _CustomNavBarState();
}
class _CustomNavBarState extends State<CustomNavBar> {
int selectedItem = 0;
#override
Widget build(BuildContext context) {
return CurvedNavigationBar(
color: Colors.white,
height: 52,
backgroundColor: MainColors.primaryColor,
animationCurve: Curves.easeInOut,
animationDuration: Duration(milliseconds: 350),
index: selectedItem,
onTap: (currItem) {
setState(() {
selectedItem = currItem;
widget.currentSelectedItem(currItem);
});
},
items: <Widget>[
Icon(Icons.home),
Icon(Icons.account_circle),
Icon(Icons.favorite)
],
);
}
}
Sorry for asking such stupid question. I'm new in Flutter and Firebase development.
Thank You.
Looking at you problem, I would suggest you to not split the Home and the navigation bar components, Kindly take a look into this, it will help you
Google Flutter Bottom Navigation Bar
We can use widget keyword when we want to refer to the method defined in the stateful class not in the state.
To solve this:
You can pass user and logout method to the state of the class.
#override
_HomeScreenState createState() => _HomeScreenState(logOut,user);
and in the state create a constructor
final Function logOut;
final User user;
_HomeScreenState(this.logOut,this.user);
Hope this will help you and can you please upload the error that it is giving.

Flutter image grid from firebase (instagram style)

I want to create a image grid like the instagram search page/profilepage with images in rows of 3 and want the grid to fill up depending on how many imgaes I have in my collection.
When i press the image I want to load that photo so you can save that photo to your phone. I've tried using the wrap function but instead of getting a list with one of every photo i get 152 sets of every photo so the list is huge.
Anyone know what ive done wrong?
And since i want to be able to use onpressed to view the entire photo maybe wrap isn't the function I should use?
Here is a copy of my code:
class Imagepost {
final String img;
Imagepost({this.img});
}
----------------------------------------------------------------
List<Imagepost> _imageListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
print(doc.data);
return Imagepost(
img: doc.data['img'],
);
}).toList();
}
Stream<List<Imagepost>> get img {
return imageCollection.snapshots().map(_imageListFromSnapshot);
}
-------------------------------------------------------------------
class ImageTile extends StatelessWidget {
final Imagepost img;
ImageTile({this.img});
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Wrap(
spacing: 1,
runSpacing: 1,
children: List.generate(img.img.length, (index) {
return Container(
width: (size.width-3)/3,
height: (size.width-3)/3,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(img.img),
fit: BoxFit.cover),
),
);
}),
);
}
}
-------------------------------------------------------------------
class MediaList extends StatefulWidget {
#override
_MediaListState createState() => _MediaListState();
}
class _MediaListState extends State<MediaList> {
#override
Widget build(BuildContext context) {
final imagepost = Provider.of<List<Imagepost>>(context) ?? [];
return ListView.builder(
itemCount: imagepost.length,
itemBuilder: (context, index) {
return ImageTile(img: imagepost[index]);
},
);
}
}
-------------------------------------------------------------
There is a widget called GridView which would be great for that.
GridView.count(
// Create a grid with 3 columns. If you change the scrollDirection to
// horizontal, this produces 3 rows.
crossAxisCount: 3,
// Generate 100 widgets that display their index in the List.
children: List.generate(100, (index) {
return Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline5,
),
);
}),
)

How do I execute FutureBuilder only once in a list that gets reinitialized?

I have created a Flutter project that has a home page with a bottom navigation bar. I used an IndexedStack as the body.
I'm trying to make my CustomList() a feed which shows the most recent documents.
I intend to use pagination too.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final widgetOptions = [
CustomList(),
Page2(),
Page3(),
Page4(),
];
int _selectedItemPosition = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
//
currentIndex: _selectedItemPosition,
onPositionChanged: (index) => setState(() {
_selectedItemPosition = index;
}),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem()
],
),
body: IndexedStack(
index: _selectedItemPosition,
children: widgetOptions,
),
);
}
}
This is the code of my CustomList():
class CustomList extends StatefulWidget {
#override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
#override
Widget build(BuildContext context) {
Future<Object> getData()
{
//get Data from server
}
return FutureBuilder<Object>(
future: getData(),
builder: (context, snapshot) {
if(snapshot.data != null)
{
if(snapshot.hasData)
{
//get Documents
}
return ListView.builder(
//
itemBuilder: (context , index) {
//return a widget that uses the data received from the snapshot
},
);
}
}
);
}
}
The issue is that every time I change the page using the bottom navigation bar, whenever I come back to my default page with the CustomList(), the FutureBuilder is fired again resulting in my list having duplicates. This is due to the CustomList() being initialized again.
How do I structure my code so that the FutureBuilder is executed only once and isn't fired repeatedly when I use the BottomNavigationBar to change the page?
This is because you get a new future every time build is called, because you pass a function call to the FutureBuilder and not a reference that stays the same.
There are several easy options to solve this.
You can store a reference to the future and pass this reference to the FutureBuilder
You can use an AsyncMemoizer from the async package to only run the future once https://api.flutter.dev/flutter/package-async_async/AsyncMemoizer-class.html
You can use the FutureProvider from the provider package https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html

Flutter/Firestore: How to add items to stream on scroll (preserve scrollposition when done fetching)?

I have a chat (ListView) with messages that I only want to load as needed.
So when the chat is initially loaded I want to load the last n messages and when the user scrolls up I want to fetch older messages also.
Whenever a new message arrives in the firebase collection it should be added to the ListView. I achieved this by using a StreamBuilder that takes the stream of the last n messages where n is a variable stored in the state that I can increase to load more messages (it is an argument to the function that gets the stream of the last n messages).
But with my current implementation the problem is that even though more messages are fetched and added to the listview when I scroll up, it then immediately jumps back to the bottom (because the listview is rebuilt and the scrollposition isn't preserved). How can I prevent this from happening?
This issue is not related to ListView or the scroll position. Those are kept with automatically. The issue must be somewhere else in your code. Check my example below to see how having a list, adding new items and then resetting it, will maintain the scroll position or move to the right place:
class ListViewStream60521383 extends StatefulWidget {
#override
_ListViewStream60521383State createState() => _ListViewStream60521383State();
}
class _ListViewStream60521383State extends State<ListViewStream60521383> {
List<String> _itemList;
#override
void initState() {
resetItems();
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: ListView.builder(
reverse: true,
itemCount: _itemList.length,
itemBuilder: (context, index){
return Container(
height: 40,
child: Text(_itemList[index]),
);
},
),
),
Row(
children: <Widget>[
RaisedButton(
onPressed: addMoreItems,
child: Text('Add items'),
),
RaisedButton(
onPressed: resetItems,
child: Text('Reset items'),
)
],
)
],
);
}
void addMoreItems(){
int _currentListCount = _itemList.length;
setState(() {
_itemList.addAll(List.generate(60, (index) => 'item ${index + _currentListCount}'));
});
}
void resetItems(){
setState(() {
_itemList = List.generate(60, (index) => 'item $index');
});
}
}
Using FirestoreListView you do that easily.
Refer this for more info https://www.youtube.com/watch?v=si6sTuVZxtw

How to use a Pagview PageController in Flutter using Redux

I am using Redux within Flutter (and I am just starting to learn both). I have been trying to figure out how to switch between the pages of a PageView using the PageView's PageController.
However, whenever I try to use the PageController.jumpToPage() function, I get an exception stating:
"The following assertion was thrown while finalizing the widget tree: setState() or markNeedsBuild() called when widget tree was locked."
When I attempt to call the PageController.jumpToPage() in my reducer, it does navigate to the page within the pageview; but the exception gets thrown.
I have also tried just building a new PageController in the reducer, and just setting the PageController's initial page property to the desired page, but that didn't seem to do anything.
I have run out of ideas on how to figure this out on my own, so I thought I would ask here. Any help would be appreciated.
I have thrown together a quick sample showing what I am trying to do:
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final store = Store<AppState>(appReducer,
initialState: AppState.initial(), middleware: []);
#override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(
title: 'PageView Example With Redux',
home: MyPageViewContainer(),
),
);
}
}
class AppState {
final List<Widget> pageViewList;
final PageController pageController;
AppState({
this.pageViewList,
this.pageController,
});
factory AppState.initial() {
return AppState(
pageViewList: [
PageOne(),
PageTwo(),
],
pageController: PageController(initialPage: 0),
);
}
AppState copyWith({
List<Widget> pageViewList,
PageController pageController,
}) {
return AppState(
pageViewList: pageViewList ?? this.pageViewList,
pageController: pageController ?? this.pageController,
);
}
}
AppState appReducer(AppState state, action) {
if (action is NavigateToPageOneAction) {
state.pageController.jumpToPage(0);
return state;
}
else if (action is NavigateToPageTwoAction) {
state.pageController.jumpToPage(1);
return state;
}
else {
return state;
}
}
class NavigateToPageOneAction {}
class NavigateToPageTwoAction {}
class MyPageView extends StatelessWidget {
final List<Widget> pageViewList;
final PageController pageController;
final Function onPageChanged;
MyPageView({
this.pageViewList,
this.pageController,
this.onPageChanged,
});
#override
Widget build(BuildContext context) {
return PageView(
controller: pageController,
children: pageViewList,
onPageChanged: onPageChanged,
);
}
}
class MyPageViewContainer extends StatelessWidget {
MyPageViewContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _MyPageViewModel>(
converter: (Store<AppState> store) => _MyPageViewModel.create(store),
builder: (BuildContext context, _MyPageViewModel vm) {
return MyPageView(
pageViewList: vm.pageViewList,
pageController: vm.pageController,
);
},
);
}
}
class _MyPageViewModel {
final List<Widget> pageViewList;
final PageController pageController;
final Function onPageChanged;
_MyPageViewModel({
this.pageViewList,
this.pageController,
this.onPageChanged,
});
factory _MyPageViewModel.create(Store<AppState> store) {
_onPageChanged() {}
return _MyPageViewModel(
pageViewList: store.state.pageViewList,
pageController: store.state.pageController,
onPageChanged: _onPageChanged(),
);
}
}
class PageOne extends StatelessWidget {
PageOne();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page One"),
),
backgroundColor: Colors.black,
body: Column(),
drawer: MyDrawer(),
);
}
}
class PageTwo extends StatelessWidget {
PageTwo();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page Two"),
),
backgroundColor: Colors.blue,
body: Column(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
MyDrawer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _MyDrawerViewModel>(
converter: (Store<AppState> store) => _MyDrawerViewModel.create(store),
builder: (BuildContext context, _MyDrawerViewModel vm) {
return Drawer(
child: ListView(
children: <Widget>[
Container(
child: ListTile(
title: Text(vm.pageOneText),
onTap: vm.pageOneOnTap,
),
),
Container(
child: ListTile(
title: Text(vm.pageTwoText),
onTap: vm.pageTwoOnTap,
),
),
],
),
);
},
);
}
}
class _MyDrawerViewModel {
final String pageOneText;
final String pageTwoText;
final Function pageOneOnTap;
final Function pageTwoOnTap;
_MyDrawerViewModel({
this.pageOneText,
this.pageTwoText,
this.pageOneOnTap,
this.pageTwoOnTap,
});
factory _MyDrawerViewModel.create(Store<AppState> store) {
_goToPageOne() {
store.dispatch(NavigateToPageOneAction());
}
_goToPageTwo() {
store.dispatch(NavigateToPageTwoAction());
}
return _MyDrawerViewModel(
pageOneText: "Page One",
pageTwoText: "Page Two",
pageOneOnTap: _goToPageOne,
pageTwoOnTap: _goToPageTwo,
);
}
}
I seem to have figured out how to solve my problem. I saw an answer in this post: Flutter: setState() or markNeedsBuild() called when widget tree was locked... during orientation change
In that post the OP was encountering the same error when changing between portrait and landscape mode while the Drawer was open. The answer in that post suggested calling Navigator.pop() (which closes the Drawer) before changing view modes.
So I gave that a try and closed my Drawer using the Navigator.pop() prior to using the PageController's .jumpToPage method. This seems to work, and allows me to navigate between pages of the PageView using onTap events from the Drawer, without throwing the "The following assertion was thrown while finalizing the widget tree: setState() or markNeedsBuild() called when widget tree was locked" exception.
I assume that this means that while the Drawer is open, the widget tree is placed into a locked state.
Hopefully this helps someone, as it took me a while to figure out.
#Blau
Sometimes an event happened outside any widgets you built. e.g. (1) A timer that increment a 'Global Counter', this counter will be shown in many pages/widgets (2) A message sent from the socket server, on receiving this message/event, the user may be anywhere(any pages/widgets), and you don't know where to 'setState' (Or the widget is actually not there because the user is not at that page)
I've built 2 examples that demonstrate how to use Redux to solve this kind of problems:
Example 1: (Use a multi-thread timer to 'setState' a widget when an external event fires)
https://github.com/lhcdims/statemanagement01
Example 2: (Use Redux to refresh a widget when an external event fires)
https://github.com/lhcdims/statemanagement02
Demo Screen Shot:

Resources