currently flutter app structure
StackedHome has a pageview with 2 children
Pageview(parent):
HomeScreen(child#1)
Vertical PageView
bottom navigation bar
UserProfilePage(child#2)
HomeScreen should pass the index value to UserProfilePage, so when scrolling horizontally, we will get user profilescreen with id passed to that received from HomeScreen. based on the id passed i will display related user profile
Here is sample video showing the problem :
https://drive.google.com/file/d/1tIypNOHewcFSo2Pf-F97hsQGfDgNVqfW/view?usp=sharing
Problem:
i managed to do that and its working fine, but my problem on setState of that variable
setState(() {
_postIndex = postIndex;
});
on each HomeScreen > onPageChanged call i am updating the index value pass it to the parent (StackedHome) class, and since there is a setState to update profile index (UserProfilePage)...the whole app will be rebuild on each pageview change...
What i need is to disable that main widget to be rebuilt again and again on value update..
StackedHome
class StackedHome extends StatefulWidget {
final int data;
final Function(int) onDataChange;
const StackedHome({
this.data,
this.onDataChange,
Key key,
}) : super(key: key);
#override
_StackedHomeState createState() => _StackedHomeState();
}
class _StackedHomeState extends State<StackedHome>{
PageController pageController;
int _count = 0;
int _postIndex = 0;
void _postId(int postIndex) {
//This cuasing main screen to be rebuilt everytime on pageview scroll
//but getting the value correctly
setState(() {
_postIndex = postIndex;
});
}
#override
void initState() {
super.initState();
pageController = PageController();
}
#override
void dispose() {
pageController.dispose();
super.dispose();
}
int index = 0;
#override
Future<void> _refreshPosts() async {
PostApi postApi = PostApi();
setState(() {
postApi.fetchAllPosts();
});
}
Widget build(BuildContext context) {
PostApi postApi = PostApi();
return FutureBuilder(
future: postApi.fetchAllPosts(),
builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return apiError('No Connection Made');
break;
case ConnectionState.waiting:
case ConnectionState.active:
return ApiLoading(color:0xff000000);
break;
case ConnectionState.done:
if (snapshot.hasError) {
return apiError(snapshot.error.toString());
}
if (snapshot.hasData) {
return _drawPostsList(snapshot.data, context);
}
break;
}
return Container();
},
);
}
Widget _drawPostsList(List<Post> posts, BuildContext context) {
return PageView(
reverse: true,
children: <Widget>[
HomeScreen(
posts: posts,
index: index,
postId: _postId,//function Passed
),
UserProfilePage(
posts: posts,
index: _postIndex,
)
],
);
}
}
HomeScreen
class HomeScreen extends StatefulWidget {
#override
final List posts;
final int index;
final Function(int) postId;
int getPage() {
return value;
}
void setPage(int page) {
value = page;
}
HomeScreen({Key key, this.posts, this.index, this.postId}) : super(key: key);
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
final PageController _controller = PageController();
PageController _pageController = PageController();
int index = 0;
#override
void initState() {
super.initState();
//Set pageview inital page
_pageController = PageController(
keepPage: true,
initialPage: widget.getPage(),
);
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refreshPosts,
child: Stack(children: <Widget>[
PageView.builder(
controller: _pageController,
onPageChanged: (index) => setState(() {
.
widget.postId(index);//I am calling parent class and updating the vlaue with new index value
.
}),
scrollDirection: Axis.vertical,
itemBuilder: (context, position) {
//Build image lists
return _homeList(widget.posts, position);
},
),
BottomNavigation("light"),
]),
);
}
}
i hope my problem is clear enough....i need to pass the value to parent so i can pass it to second child which is the profile screen so it will show user profile realted to that post
Ohh wow, managed to solve this problem using provider and consumer, by listening to any update on index id... this post helped me to solve it https://medium.com/flutter-nyc/a-closer-look-at-the-provider-package-993922d3a5a5
Related
I am new to Flutter and stumped on how to do this. I have a screen that has a Carousel Slider widget on it that I am holding in a separate file/widget to keep the code as clean as possible. To that Carousel I am passing a List which are urls of images and videos. I have already implemented an indicator bar and have the index of the list held within activeIndex variable within the Carousel widget. I then need to pass that index value to a separate widget held in a variable on the main page of my app (one with the clean code).
I basically need help on where to define variables in one widget that I can then define and pass to multiple other widgets. Please let me know if you need more context as I am new to coding in general. Thanks!
Carousel Widget
class AssetCarouselBuilder extends StatefulWidget {
const AssetCarouselBuilder({
#required this.assets,
this.activeIndex
});
final List<String> assets;
final int activeIndex;
#override
State<AssetCarouselBuilder> createState() => _AssetCarouselBuilderState();
}
class _AssetCarouselBuilderState extends State<AssetCarouselBuilder> {
int activeIndex = 0;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CarouselSlider.builder(itemCount: widget.assets.length,
itemBuilder: (context, index, realIndex){
final assetUrl = widget.assets[index];
options: CarouselOptions(
onPageChanged: (index, reason) =>
setState (() => activeIndex = index)
//this is where I am holding the variable 'activeIndex' that I need elsewhere
if (assetUrl.contains('jpg')) {
return buildImage(assetUrl, index);
}
const SizedBox(height: 5),
buildIndicator(),
Widget buildImage(String imageUrl, int index) => Image(),
Widget buildIndicator() => AnimatedSmoothIndicator(
activeIndex: activeIndex,
count: widget.assets.length,
effect: ColorTransitionEffect()
Implementation of Carousel on "main page"
class FeedPageWidget extends StatefulWidget {
const FeedPageWidget({
Key key,
}) : super(key: key);
#override
_FeedPageWidgetState createState() => _FeedPageWidgetState();
}
class _FeedPageWidgetState extends State<FeedPageWidget>
int _currentIndex = 0;
AssetCarouselBuilder(assets: listViewPostsRecord.postAssets.asList())
And then widget I need to pass the index to another widget on the "main page".
ShareMenuWidget(
postRef: listViewPostsRecord,
assetIndex: _currentIndex)
Any help on how I get the "activeIndex" value on the setState function in the Carousel slider is very appreciated!
You can use callback method like Function(int activeIndex)? onIndexChanged;.
class CarouselCW extends StatefulWidget {
final Function(int activeIndex)? onIndexChanged;
const CarouselCW({
Key? key,
this.onIndexChanged,
}) : super(key: key);
#override
State<CarouselCW> createState() => _CarouselCWState();
}
class _CarouselCWState extends State<CarouselCW> {
final CarouselController carouselController = CarouselController();
#override
Widget build(BuildContext context) {
return CarouselSlider.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int index, int realIndex) {
return Text(
index.toString(),
);
},
options: CarouselOptions(
onPageChanged: (index, reason) {
if (widget.onIndexChanged != null) widget.onIndexChanged!(index);
},
),
);
}
}
And while using this widget you will get
CarouselCW(
onIndexChanged: (activeIndex) {
print(activeIndex.toString());
},
)
i am clearing a hive box and updating it in the very next line.
The update can be seen in the same file code (through debugging).
But somehow for the other stateless widget/class/file box is empty.
boxT.clear() ;
setState(() {
boxT.addAll({
[count2, totalEntries]
});});
Reason for clearing the box on everytime a specific button is pressed: I am adding a map. addAll() simply creates another entry. i dont want it. i have also tried put and putall but they are only showing the value and not the key.
That's because Hive is NOT a state management tool, if you add/remove something to/from your box, you need to let your state know. You should have a global state that listens to changes from that Box.
Example with a ValueNotifier.
class ItemsNotifier extends ValueNotifier<List<Item>> {
ItemsNotifier() : super(getItemsFromBox());
List<Item> _items = getItemsFromBox();
#override
List<Item> get value => _items;
void addItem(Item item) {
addItemToBox(item);
_items = getItemsFromBox();
notifyListeners();
}
Future<void> deleteAll() async {
await clearBox();
_items = getItemsFromBox();
notifyListeners();
}
}
List<Item> getItemsFromBox() {
return Hive.box<Item>('items').values.toList();
}
Future<void> addItemToBox(Item item) async {
await Hive.box<Item>('items').add(item);
}
Future<void> clearBox() async {
await Hive.box<Item>('items').clear();
}
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
#override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
late final ItemsNotifier _notifier;
#override
void initState() {
_notifier = ItemsNotifier();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ExampleWidget(notifier: _notifier),
floatingActionButton: FloatingActionButton(
onPressed: _notifier.deleteAll,
child: const Icon(Icons.delete),
),
);
}
#override
void dispose() {
_notifier.dispose();
super.dispose();
}
}
class ExampleWidget extends StatelessWidget {
const ExampleWidget({required this.notifier, Key? key}) : super(key: key);
final ItemsNotifier notifier;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<Item>>(
valueListenable: notifier,
builder: (context, items, _) {
return ListView.builder(
itemBuilder: (context, index) => ListTile(
title: Text(items[index].title),
),
itemCount: items.length,
);
},
);
}
}
Note that if you have multiple pages using the data from this box, I would recommend using something like Provider and having a ChangeNotifier instead of the ValueNotifier or using flutter_bloc and having a Repository that handles communication with Hive.
I'm new to flutter and trying to use two Stateful Widgets first one calling the second in build() method and I just want to update the child widget variable that is passed from parent in the constructor.
Here is the code I'm trying with.
Parent Widget
class Parent extends StatefulWidget {
Parent({Key? key}) : super(key: key);
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
List appointments = [];
#override
void initState() {
super.initState();
fetchAppointments();
}
#override
Widget build(BuildContext context) {
return Builder(builder: (BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
_pullRefresh();
},
child: ListView(
children: [
AppointmentsWidget(appointments: appointments) // <----- I passed the variable in constructor and this one is updating in setState and I want it to update in the child widget too
],
),
),
);
});
}
_pullRefresh() {
fetchAppointments();
}
fetchAppointments() {
setState(() {
// Stuff to do
appointments = ......
......
......
});
}
}
Child Widget
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
#override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(appointments: appointments); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
var appointments;
_AppointmentsWidgetState({this.appointments}); // <--- Constructor 2
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
I know the constructor calls once but I couldn't find a way to either recall the constructor OR somehow pass the updated value to the constructor.
Your help is really appreciated.
You should make your child widget stateless, as its state (the appointments) are handled by the parent. What happens currently is that your child widget is constructed, where the empty list is used as its widget.appointments value. Then when the appointments have been fetched, the widget.appointments rebuilds, but since the state of the child is maintained, this value is not passed on (initState of the child is not re-run).
class AppointmentsWidget extends StatelessWidget {
final List appointments;
const AppointmentsWidget({Key? key, required this.appointments}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
Also, take a look at the flutter docs on handling state:
https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro
These also state that it's good to keep your state high up (in the parent in this case), and make child widgets use the state of the parents to render themselves appropriately.
Rectification
As you mention in the comments, you need the child widget to be stateful (for maintaining state on some other data). In that case, you can simply get rid of the appointments state variable and use widget.appointments instead, which will update when the parent rebuilds with a new value.
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
#override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
_AppointmentsWidgetState(); // <--- Constructor 2
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(widget.appointments[index].toString()); // <--- This is where I use it
},
);
}
}
You need some listenable to update the widget because the context of parent and child are diferents. In that case the correct way to do it (without a state managment package) is with a InheritedWidget.
Inherited class:
class ExampleInherited extends InheritedWidget {
final Widget child;
final ExampleBloc exampleBloc; // <-- change notifier that can do changes and notify their children
const ExampleInherited({Key? key, required this.child, required this.exampleBloc}) : super(key: key, child: child);
static ExampleInherited? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ExampleInherited>();
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => true;
}
ChangeNotifier class:
class ExampleBloc extends ChangeNotifier {
static final ExampleBloc _exampleBloc = ExampleBloc._internal();
factory ExampleBloc() {
return _exampleBloc;
}
ExampleBloc._internal();
exampleMethod(){
// here you can do whatever you need (update vars)
notifyListeners(); // <-- this notifies the children that they need to be rebuilded
}
}
Then set this in your parent view:
ExampleInherited(
exampleBloc: ExampleBloc(),
child: // content
}
And then in your child view:
#override
Widget build(BuildContext context) {
ExampleBloc exampleBloc = ExampleInherited.of(context)!.exampleBloc;
return AnimatedBuilder(
animation: exampleBloc,
builder: (context, child) {
return //your content
// this will rebuild every time you call notifyListeners() in your bloc
})
}
i use RefreshIndicator and its working well but couldnt make it functional for my database, i think my setstate is wrong , can you help me to use setstate to make it functional , here is my product page..
class FeaturedProducts extends StatefulWidget {
#override
_FeaturedProductsState createState() => _FeaturedProductsState();-
}
class _FeaturedProductsState extends State<FeaturedProducts> {
final keyRefresh = GlobalKey<RefreshIndicatorState>();
List<ProductModel> products = [];
#override
void initState() {
super.initState();
loadProducts();
}
Future loadProducts() async {
keyRefresh.currentState?.show();
await Future.delayed(Duration(milliseconds: 4000));
setState(() => this.products = products);,
}
#override
Widget build(BuildContext context) {
final productProvider = Provider.of<ProductProvider>(context);
return RefreshIndicator(
keyRefresh: keyRefresh,
onRefresh: loadProducts,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: productProvider.products.length,
itemBuilder: (_, index) {
return FeaturedCard(
product: productProvider.products[index],
);
}));
}
}
I have the following issue with my 'workout' App using multiple workoutlists with various workoutitems:
I select a workoutlist with 12 workoutitems.
The 'activity' screen with the AnimatedList is shown.
Afterwards, I select a different workoutlist with 80 workoutitems.
The AnimatedList is now showing the new workoutlist but only the first 12 workoutitems.
Why?
I thought that the AnimatedList inside the build Widget is rebuild every time (I am not using GlobalKey).
class WorkoutListView extends StatelessWidget {
const WorkoutListView({this.filename});
final String filename;
#override
Widget build(BuildContext context) {
return Selector<WorkoutListModel, List<Workout>>(
selector: (_, model) => model.filterWorkouts(filename),
builder: (context, workouts, _) {
return AnimatedWorkoutList(
list: workouts,
);
},
);
}
}
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
Key key,
#required List<Workout> list,
}) : _list = list,
super(key: key);
final List<Workout> _list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget._list.length,
itemBuilder: (context, index, animation) {
final workout = widget._list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}
try this:
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
#required List<Workout> list,
});
final List<Workout> list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget.list.length,
itemBuilder: (context, index, animation) {
final workout = widget.list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}