I try to pass the value of my carouselwidget index to another widget . however the value returned in my second widget is null ( so for example if i drag a value in my carousel the getindex function prints the index value but the print test in the background_widget stay initialize as null
I think the pb is that i trie to passe a variable (index) from a child (carousel) to an another (Map) through the common parent (Screen stacking carousel and map together).
I'm trying to pass the index now with help of a provider but without success (and seems really overkill to build a model & a provider just to pass a f**ing variable between too child at the same level):
import 'package:provider/provider.dart';
import 'package:algua_alpha/provider/provideIndex.dart';
/*All the variables definition are not published here */
//// model class for index
class Index {
final int id;
Index(this.id);
}
class ProvideIdx with ChangeNotifier {
Index _index;
Index get index {
print(_index);
return _index;
}
void changeId() {
notifyListeners();
}
}
class RegionSelection extends StatefulWidget {
final String region;
RegionSelection(this.region);
#override
_RegionSelectionState createState() => _RegionSelectionState(region);
}
class _RegionSelectionState extends State<RegionSelection> {
// main screen where Carousel and map are stacked
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (ctx) => ProvideIdx(),
child: Scaffold(
body: Container(
child:!isLoading?
Stack(
children: <Widget>[
Maposm(region,// this widget need the Index of the carousel slider
),
Align(
alignment: Alignment.bottomCenter,
child: CarouselSlider.builder( //========= Carousel
itemBuilder: (BuildContext c, index) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewScreen(
objectlist: region,
item: index,
)));
},
);
itemCount: region.length,
options: CarouselOptions(
enableInfiniteScroll: true,
scrollDirection: Axis.horizontal,
enlargeCenterPage: true,
onPageChanged: (index, reason) { // this callback initialize the new index value
setState(() {
Index(index);
});
},
);
}
)
])
: Center(
child: CircularProgressIndicator(
backgroundColor: Colors.white,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.orange[300]))
),
);
}
}
//=============================== The Map ===============================
class Maposm extends StatefulWidget {
//final carouselID;
final region;
Maposm(this.region);
#override
_MaposmState createState() =>_MaposmState(region);
}
class _MaposmState extends State<Maposm> {
#override
Widget build(BuildContext context) {
final indexes = Provider.of<Index>(context);
final carouselID = indexes.id; //get carousel id
List<Marker> markers = [];
for (var i = 0; i < region.length; i++) {
var m = Marker(
point:
LatLng(region[i].xy[0], region[i].xy[1]),
builder: (ctx) => InkWell(
child: (carouselID == i // index of marker builder == index of carousel
? Icon(
Icons.assistant,
color: Colors.pink[700],
size: 20,
)
: Icon( // else
Icons.brightness_1_sharp,
color: Colors.black87,
size: 4,
)),
onDoubleTap: null,
));
markers.add(m);
}
return FlutterMap(
options: MapOptions(
interactive: true,
slideOnBoundaries: true,
bounds: LatLngBounds(
LatLng(coordxy[0], coordxy[1]), LatLng(coordxy[2], coordxy[3])),
),
layers: [
TileLayerOptions(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c']),
MarkerLayerOptions(markers: markers)
]);
}
}
Related
I am trying to build a view/route that will list items fetched from a REST source.
I want to show a notification item below the list while the data is being fetched.
But my ListView builder is constructed around the fetched data's structure, so I figured just have a ListTile fit some appropriate UX elements below the generated list inside a Column - which was kinda working great - or so I thought - until the list grows to fill the screen causing RenderFlex overflowed error. Wrapping the ListView builder in Expanded fixed that but moved the indicator to the bottom of the screen.
In trying to fix it I seem to have broken more of the plumbing and the boolean variable that should control the idicator widget; isLoading: stockSet.isBusyLoading doesn't seem to update.
At the moment if I hardcode it as `` it does sit in the appropraite position but I am back with the RenderFlex overflow.
Once all of this is working I'll be wanting to automatically load items untill the screen is full - not sure where I'll be triggering that from yet.
class MyStockSet extends StatefulWidget {
const MyStockSet({super.key});
static const indexStr = 'stocks';
static const labelStr = 'Properties';
#override
State<MyStockSet> createState() => _MyStockSetState();
}
class _MyStockSetState extends State<MyStockSet> {
#override
Widget build(BuildContext context) {
const String imagePath = 'assets/images/${MyStockSet.indexStr}.png';
var assetImage = const AssetImage(imagePath);
//var stockSet = context.watch<StockSet>(); <- didn't work either
var stockSet = Provider.of<StockSet>(context,listen: false);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
AscHero(
assetImage: assetImage,
tag: MyStockSet.indexStr,
title: MyStockSet.labelStr,
radius: 32,
),
const SizedBox(width: 12),
const Text(MyStockSet.labelStr),
],
),
actions: [
IconButton(
onPressed: () {
var stockSet = context.read<StockSet>();
int newNr = stockSet.stocks.length + 1;
Stock tmpstock = Stock(
id: newNr,
title: 'test$newNr',
thumbUrl: 'url',
description: 'desc');
stockSet.add(tmpstock);
},
icon: const Icon(Icons.add),
),
IconButton(
onPressed: () {
developer.log('btn before isBusyLoading ${stockSet.isBusyLoading}');
stockSet.fetch();
developer.log('after btn isBusyLoading ${stockSet.isBusyLoading}');
},
icon: const Icon(Icons.handshake),
),
],
),
body: Column(
children: [
Row(
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: () => developer.log('Btn pressed.'),
child: Text('Btn')),
],
),
Expanded(
child: Column(children: [
_StockListView(),
LoadingStockListItemNotif(
isLoading: true,
),
]),
),
],
),
);
}
}
class _StockListView extends StatefulWidget {
#override
State<_StockListView> createState() => _StockListViewState();
}
class _StockListViewState extends State<_StockListView> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
developer.log('_StockListView didChangeDependencies()');
// developer.log('scroll pos ${_scrollController.position}');
}
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return ListView.builder(
// controller: _scrollController,
shrinkWrap: true,
itemCount: stockSet.stocks.length,
itemBuilder: (context, index) => InkWell(
child: StockListItem(
stock: stockSet.stocks[index],
),
onTap: () => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(stockSet.stocks[index]),
),
),
);
}
void _scrollListener() {
developer.log('_scrollListener');
}
}
and
class StockSet extends ChangeNotifier {
final List<Stock> _stocks = [];
late bool isBusyLoading = false;
List<Stock> get stocks => _stocks;
void add(Stock stock) {
_stocks.add(stock);
developer.log('added stock :${stock.title}');
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
Future<void> fetch() async {
developer.log('fetch() iL T');
isBusyLoading = true;
notifyListeners();
Stock tmpStock = await _fetchAStock();
developer.log('fetch() iL F');
isBusyLoading = false;
notifyListeners();
add(tmpStock);
}
Future<Stock> _fetchAStock() async {
developer.log('fetch stock ');
final response = await http.get(
Uri.https(
//...
),
);
developer.log('response.statusCode:${response.statusCode}');
if (response.statusCode == 200) {
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
}
throw Exception('error fetching stocks:');
}
}
Apologies for the convoluted question.
Add mainAxisSize : MainAxisSize.min for the column inside the expanded widget. The expanded doesn't have any bounds and that's why it throws an error. You can wrap the column with a SingleChildScrollView if you have long content to display
This worked for me!
Just set the shrinkWrap attribute to true
Main lesson:
Don't fight the framework.
Answer:
Instead of tying yourself into Möbius knots trying to put the ListView's functionality outside of itself; use the fact that the ListView.builder allows you to sculpt the logic of how it gets built and what it will contain - given that the provider can trigger its rebuild when the variable in the data set changes.
In other words; by increasing the loop of the builder, you can insert a kind of footer to the Listview. The appearance (or not) of that can depend on the provider, provided it fires the appropriate notifyListeners()s etc.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:equatable/equatable.dart';
import 'dart:async';
class ItemSetRoute extends StatefulWidget {
const ItemSetRoute({Key? key}) : super(key: key);
#override
State<ItemSetRoute> createState() => _ItemSetRouteState();
}
class _ItemSetRouteState extends State<ItemSetRoute> {
#override
Widget build(BuildContext context) {
var itemSet = Provider.of<ItemSet>(
context,
listen: true /* in order to rebuild */,
);
return Scaffold(
appBar: AppBar(title: const Text('Test'), actions: [
IconButton(
onPressed: () {
itemSet.fetch();
},
icon: const Icon(Icons.download),
)
]),
body: Column(
//screen
children: [
Row(
children: [
ElevatedButton(
onPressed: () {
itemSet.fetch();
},
child: const Text('Btn')),
],
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemSet.items.length + 1,
itemBuilder: (context, index) {
/* logic here to create a kind of footer of the ListView */
if (index <= itemSet.items.length - 1) {
return InkWell(
child: ItemListItem(
item: itemSet.items[index],
),
onTap: () {
//('Item tapped, navigate etc.');
});
} else {
return LoadingItemNotifier(
isLoading: itemSet.isBusyLoading,
);
}
},
),
),
],
),
);
}
}
//Models
class ItemListItem extends StatelessWidget {
const ItemListItem({Key? key, required this.item}) : super(key: key);
final Item item;
#override
Widget build(BuildContext context) {
return Material(
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
),
);
}
}
class LoadingItemNotifier extends StatefulWidget {
const LoadingItemNotifier({Key? key, this.isLoading = false})
: super(key: key);
final bool isLoading;
#override
State<LoadingItemNotifier> createState() => _LoadingItemNotifierState();
}
class _LoadingItemNotifierState extends State<LoadingItemNotifier> {
#override
Widget build(BuildContext context) {
if (widget.isLoading) {
return Material(
child: ListTile(
leading: SizedBox(
width: 48,
height: 48,
child: ClipOval(
child: Material(
color: Colors.lightBlue.withOpacity(0.25),
child: const Center(
child: Icon(Icons.download),
),
),
),
),
title: const Text('Loading'),
isThreeLine: true,
subtitle: const Text('One moment please...'),
dense: true,
),
);
} else {
return const SizedBox(height: 0);
}
}
}
class ItemSet extends ChangeNotifier {
final List<Item> _items = [];
late bool isBusyLoading = false;
List<Item> get items => _items;
void add(Item item) {
_items.add(item);
notifyListeners();
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
Future<void> fetch() async {
isBusyLoading = true;
notifyListeners();
/* handling REST call here */
await Future.delayed(const Duration(milliseconds: 500));
Item newItem = const Item(id: 123, title: 'Title', description: 'Desc');
isBusyLoading = false;
add(newItem);
}
}
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
Caveats
I don't know if this is the most efficient way of doing this - perhaps there should be fewer states, etc. ...
I tried making a growable list which will contain the salary that have been calculated from other pages. But when I tried to insert the latest salary into the list, the previous salary was rewritten as the latest added salary, so I ended up with 2 same item in the list. So I ended up with 2 same items inside the list. My objective here is to just make a screen with a growable list where the counter increases when I pushed the button from the previous page. Here is my code:
`class HistoryPage extends StatefulWidget {
HistoryPage ({Key key, this.title}) : super(key : key);
final String title;
#override
_HistoryPageState createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
List<String> items = [];
dynamic salary;
dynamic counter = 0 ;
#override
void initState() {
items = addItem(counter, salary);
salary = globals.payment;
counter = globals.counter;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
backgroundColor: Colors.blue[800],
title: Text("Your Salary History"),
centerTitle: true,
elevation: 0,
),
body: ListView.builder(
itemCount: counter,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(salary),
),
);
},
),
);
}
List addItem(int counter, String salary) {
setState(() {
items = List.from(items)
..insert(counter, salary);
}
);
}
}`
Here is the code for the page that increase the counter for my list:
import 'package:flutter/material.dart';
import 'package:flutter_auth/Screens/History/components/body.dart';
import 'package:flutter_auth/Screens/Payment/Components/background.dart';
import 'package:flutter_auth/components/rounded_button.dart';
import 'package:flutter_auth/Screens/PaymentDuration/paymentduration.dart';
import 'package:flutter_auth/globals.dart' as globals;
class PaymentSelection extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
// This size provide us total height and width of our screen
return Background(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: size.height * 0.05),
RoundedButton(
text: "CALCULATE PAYMENT",
press: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return PaymentDuration();
},
),
);
},
),
RoundedButton(
text: "HISTORY",
press: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return HistoryPage();
},
),
);
incrementCounter();
},
),
],
),
),
);
}
}
int incrementCounter() {
globals.counter = globals.counter + 1;
}
I started to use providers but I have a problem. I want to get the index of items that are in an other list in an other screen. How can i get them ? I have two screens: a home screen and a favorite screen and I have a listView in each. I want to get the index of the item in the home screen when it is remove from the favorite screen. This is the link of my code on GitHub : https://github.com/Rianou20/my_app_from_scratch/tree/master/my_app_from_scratch. And some relevant parts of my code :
favModel.dart
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
addInFavorite(title, description, index){
Item item = Item(title: title, description: description, );
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
notifyListeners();
}
implement(){
isInFav.add(false);
}
}
favorite_screen.dart
class Favorite extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorite'),
),
body: Consumer<FavModel>(
builder: (context, favModel, child) {
return ListView.builder(
itemCount: favModel.favList.length,
itemBuilder: (context, index) {
return TextObject(favModel.favList[index].title,
favModel.favList[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
Icons.favorite,
color: Colors.red,
size: 32,
),
onTap: () {
favModel.removeOfFavorite(index, index);
}),
),
});
},
),
);
}
}
home_screen.dart
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return Favorite();
},
),
),
),
],
),
body: Consumer<FavModel>(builder: (context, favModel, child) {
return ListView.builder(
shrinkWrap: false,
itemCount: itemData.length,
itemBuilder: (context, index) {
favModel.implement();
return TextObject(
itemData[index].title, itemData[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
favModel.isInFav.elementAt(index)
? Icons.favorite
: Icons.favorite_border,
color:
favModel.isInFav[index] ? Colors.red : null,
size: 32,
),
onTap: () {
favModel.isInFav[index]
? null
: Provider.of<FavModel>(context,
listen: false)
.addInFavorite(
itemData[index].title,
itemData[index].description,
index,
);
}),
);
});
}),
);
}
}
Where I want to get the index is in the favorite_screen.dart at this line favModel.removeOfFavorite(index, index);
Without knowing the exact use case, you can potentially store the removed values in a list and use them on your home screen.
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
List<int> _removedItemIndexList = []
get removedItemIndexList => _removedItemIndexList;
addInFavorite(title, description, countdown, imageURL, index){
Item item = Item(title: title, description: description, countdown:countdown, imageURL: imageURL);
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
_addToRemovedIndexList(index);
notifyListeners();
}
void _addToRemovedIndexList(int index) {
_removedItemIndexList.add(index);
}
implement(){
isInFav.add(false);
}
}
And then use on home_sreen.dart as
...
body: Consumer<FavModel>(builder: (context, favModel, child) {
List<int> removedIndexes = favModel.removedItemIndexList;
return ListView.builder( ... ) };
Note that the FavModel provider class must be lifted above then home_screen.dart on the widget tree in order to be able to access its values. i.e. you would want to do something like this in your main.dart
...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: FavModel(),
),
],
child: MaterialApp(...
I am trying to retrieve data from API, that's works nice.
After that I want to show my data in a ExpansionPanelList, which is builded by a method:
class _CartaPageState extends State<CartaPage> {
#override
Widget build(BuildContext context) {
// Nos suscribimos al provider
final productoService = Provider.of<ProductoService>(context);
final List<Producto> productos = productoService.productos;
_productosItems = productosToItem(productos);
return Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
child: ListView(
children: [
ExpansionPanelList(
animationDuration: Duration(milliseconds: 300),
expansionCallback: (int index, bool isExpanded) {
setState(() {
_productosItems[index].isExpanded = !isExpanded;
//productosItems[index].isExpanded = !productosItems[index].isExpanded;
});
},
//children: productosToItem(productoService.entrantes).map<ExpansionPanel>((Item item) {
children: _productosItems.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
................
The data is shown perfect, but the state is not refreshing on my ItemModel, I think the problem is because the widget is redrawing each time I touch the panel list, that retrieve (again) data from the API and never changes the state.
How can I resolve it?
Thank you in advance
EDIT: CartaPage is wraped by:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ProductoService()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Material App',
home: CartaPage()
),
);
}
}
EDIT 2:
I agree I am losing state, this it the method to convert Product to Item:
List<Item> productosToItem(List<Producto> productos) {
return List.generate(productos.length, (index) {
return Item(
headerValue: productos[index].tipo,
expandedValue: productos[index].nombre,
);
});
}
Is ExpansionPanel having its isExpanded set to item.isExpanded?
You get your isExpanded state from whatever productosToItem() generates.
When you call setState you queue a new build, which will call productosToItem() again. Without knowing what that method does, I cannot help much.
I would suggest you look into productosToItem and why it isn't setting isExpanded to the correct value.
If _productosItems[index].isExpanded isn't a setter, I would imagine you are losing the state.
EDIT 1:
You can create an internal state list that can persist the expanded state:
class Item {
Item({
this.expandedValue,
this.headerValue,
this.producto,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
Producto producto; // <------------- ADDED
bool isExpanded;
}
class _CartaPageState extends State<CartaPage> {
Map<Producto, bool> expanded = {}; // <------------- ADDED
#override
Widget build(BuildContext context) {
// Nos suscribimos al provider
final productoService = Provider.of<ProductoService>(context);
final List<Producto> productos = productoService.productos;
// NOTE: ----------- converted to a local variable
final _productosItems = productosToItem(productos);
return Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
child: ListView(
children: [
ExpansionPanelList(
key: ValueKey(productos.length), // <------------- ADDED
animationDuration: Duration(milliseconds: 300),
expansionCallback: (int index, bool isExpanded) {
// NOTE: ----------- updated
final producto = productos[index];
setState(() {
expanded[producto] = !isExpanded;
});
},
children: _productosItems.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
isExpanded: expanded[item.producto], // <------------- ADDED
canTapOnHeader: true,
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
),
);
}).toList(),
),
],
),
),
);
}
List<Item> productosToItem(List<Producto> productos) {
// keep a list of previous map
final toRemove = Map<Producto, bool>.from(expanded);
final items = List.generate(productos.length, (index) {
final producto = productos[index];
// set initial expanded state
expanded.putIfAbsent(producto, () => false);
// the item will be retained
toRemove.remove(producto);
return Item(
headerValue: producto.tipo,
expandedValue: producto.nombre,
producto: producto,
isExpanded: expanded[producto],
);
});
if (toRemove.isNotEmpty) {
// cleanup unused items
expanded.removeWhere((key, _) => toRemove.containsKey(key));
}
return items;
}
}
The key: ValueKey(productos.length), is needed, since ExpansionPanelList acted weirdly with magically appearing or disappearing items.
I'm new to Flutter Redux, I got a problem and I have no idea how to deal with it at all! I extracted the main code to keep this simple - tap indicators to switch PageView, scroll PageView to synchronise the indicator. Here is my code:
app state:
class AppState {
final List menuList;
final int currentIndex;
AppState({this.menuList, this.currentIndex});
}
the reducers:
AppState appReducer(AppState state, Object action) {
return AppState(
menuList: menuListReducer(state.menuList, action),
currentIndex: currentIndexReducer(state.currentIndex, action));
}
final menuListReducer = combineReducers<List>(
[TypedReducer<List, SetMenuListAction>(_setMenuList)]);
List _setMenuList(List menuList, SetMenuListAction action) {
menuList = action.menuList;
return menuList;
}
final currentIndexReducer = combineReducers<int>(
[TypedReducer<int, SetCurrentIndexAction>(_setCurrentIndex)]);
int _setCurrentIndex(int currentIndex, SetCurrentIndexAction action) {
currentIndex = action.index;
return currentIndex;
}
the action:
class SetMenuListAction {
List menuList;
SetMenuListAction(this.menuList);
}
class SetCurrentIndexAction {
int index;
SetCurrentIndexAction(this.index);
}
the main logic:
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState(menuList: [
{
'picUrl': 'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'description': 'this is the first image'
},
{
'picUrl': 'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
'description': 'this is the second image'
},
{
'picUrl':
'http://img4.imgtn.bdimg.com/it/u=3434394339,2114652299&fm=214&gp=0.jpg',
'description': 'this is the third image'
},
{
'picUrl': 'http://pic1.win4000.com/pic/2/07/8c57e143b1.jpg',
'description': 'this is the fourth image'
},
], currentIndex: 0),
);
runApp(App(
store: store,
));
}
// App
class App extends StatelessWidget {
final Store<AppState> store;
const App({Key key, this.store}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(title: 'Flutter redux example', home: MyDetail()),
);
}
}
class MyDetail extends StatefulWidget {
#override
_MyDetailState createState() => _MyDetailState();
}
class _MyDetailState extends State<MyDetail> with TickerProviderStateMixin {
PageController _controller;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, int>(
converter: (store) => store.state.currentIndex,
onDidChange: (newIdx) {
//this won't work because the _controller hasn't been attached to PageView
_controller.jumpToPage(newIdx);
},
builder: (BuildContext context, int idx) {
return StoreConnector<AppState, List>(
converter: (store) => store.state.menuList,
onDidChange: (newList) {
//maybe do something further
},
builder: (BuildContext context, List menus) {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
//pageview
Expanded(
child: PageView(
children: menus.map((item) {
return Column(
children: <Widget>[
Image.network(item['picUrl']),
Text(
item['description'],
style: TextStyle(fontSize: 24.0),
)
],
);
}).toList(),
onPageChanged: (int index) {
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(index));
},
physics: BouncingScrollPhysics(),
),
),
//indicators
Container(
margin: EdgeInsets.only(bottom: 50.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: menus
.asMap()
.map((i, item) => MapEntry(
i,
GestureDetector(
onTap: () {
//this won't work either maybe because the widgets is rebuilding
_controller.jumpToPage(i);
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(i));
},
child: Container(
width: 10.0,
height: 10.0,
color: i == idx
? Colors.purpleAccent
: Colors.blue,
margin: EdgeInsets.only(right: 10.0),
),
)))
.values
.toList(),
),
)
],
),
);
},
);
},
);
}
}
Sorry for the long code, but I think maybe this can help to understand my problem:
When I tap the indicator, I want to synchronise the PageView, that is _controller.jumpToPage(i), but it will show Errors. So how to make this work?
I can change the currentIndex in another screen, how to synchronise the PageView?
Is there any method to watch the state changes(separately, not the whole state) and do something?
After debugging your code I found that you are missing controller: _controller in PageView, this should fix it:
Expanded(
child: PageView(
controller: _controller,
children: menus.map((item) {
return Column(
children: <Widget>[
Image.network(item['picUrl']),
Text(
item['description'],
style: TextStyle(fontSize: 24.0),
)
],
);
}).toList(),
onPageChanged: (int index) {
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(index));
},
physics: BouncingScrollPhysics(),
),
),