flutter multiple pageview and controller - flutter

I have two pageviews:
pageview0 has 3 pages: 1, 2, 3
pageview1 has 3 pages: A, B, C
Only one pageview is shown at a time, and I want to switch between them when I press a 'switch' button on AppBar.
What I want:
If I'm at pageview0 page2 and press the switch button,
then I want to see pageview1 page A.
But the below code shows pageview1 page B (the page with same index).
I also tried to use two PageControllers instead of one, but the behavior is same.
How can I control what page to show when there's multiple PageViews and switching between them?
My code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> pageview0 = ["1", "2", "3"];
List<String> pageview1 = ["A", "B", "C"];
int pageViewIndex = 0;
// Try single controller.
PageController controller;
// Try multiple controller.
List<PageController> controllers;
#override
void initState() {
super.initState();
controller = PageController(initialPage: 0); // pageview0 and pageview1
controllers = [];
controllers.add(PageController(initialPage: 0)); // pageview0
controllers.add(PageController(initialPage: 0)); // pageview1
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: const Icon(Icons.navigate_next), onPressed: () {
setState(() {
if (pageViewIndex == 0) pageViewIndex = 1;
else pageViewIndex = 0;
});
},)
],
),
body: PageView.builder(
itemBuilder: (context, page) {
if (pageViewIndex == 0) return Center(child: Text(pageview0[page]));
else return Center(child: Text(pageview1[page]));
},
itemCount: 3,
// single controller
// controller: controller,
// Multiple controller
controller: (pageViewIndex == 0)? controllers[0] : controllers[1],
),
);
}
}

Based on Taufik's comment, I changed to IndexedStack and works well.
Here's the new code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> pageview0 = ["1", "2", "3"];
List<String> pageview1 = ["A", "B", "C"];
int pageViewIndex = 0;
// Try single controller.
PageController controller;
// Try multiple controller.
List<PageController> controllers;
#override
void initState() {
super.initState();
controller = PageController(initialPage: 0); // pageview0 and pageview1
controllers = [];
controllers.add(PageController(initialPage: 0)); // pageview0
controllers.add(PageController(initialPage: 0)); // pageview1
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: const Icon(Icons.navigate_next), onPressed: () {
setState(() {
if (pageViewIndex == 0) pageViewIndex = 1;
else pageViewIndex = 0;
});
},)
],
),
body: IndexedStack(
index: pageViewIndex,
children: <Widget> [
PageView.builder(
itemBuilder: (context, page) {
return Center(child: Text(pageview0[page]));
},
itemCount: 3,
controller: controllers[0],
),
PageView.builder(
itemBuilder: (context, page) {
return Center(child: Text(pageview1[page]));
},
itemCount: 3,
controller: controllers[1],
),
],
),
);
}
}

Related

How to rebuild only specific item in List (RxList) after updating it properties

I have problem with updating my list after updating one item. I'm using GetX for state managment. It works fine when I add an item to my list, but not when i update some element.
But I want to rebuild also specific element (TestInternalWidget) when I change some property in list. I want to avoid rebuilding the entire list.
For testing when I double click on an element, a new element should be added (It works). And when I hold down a specific element, it should change
Here is my controller file "testcontroller.dart" for testing with item model class:
import 'package:get/get.dart';
class item {
String a = "";
String b = "";
item(String a, String b)
{
this.a = a;
this.b = b;
}
}
class TestController extends GetxController {
var itemsList = <item>[].obs;
#override
void onInit() {
print("init controller");
fetchItems();
super.onInit();
}
updateItem(int index){
print("UPDATING");
itemsList[index].a = "xxxxx";
itemsList[index].b = "XXXXX";
}
void fetchItems() async{
print("fetching items");
var item1 = item("aaaaa", "AAAAA");
var item2 = item("bbbbb", "BBBBB");
itemsList.add(item1);
itemsList.add(item2);
}
addItem()
{
print("ADDING");
var newItem = item("ccccc", "CCCCCC");
itemsList.add(newItem);
}
}
Here is my "main.dart" file:
import 'package:camera_app/views/widgets/testwidget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controllers/testcontroller.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final TestController testController = Get.put(TestController());
return GetMaterialApp(
title: 'test',
home: Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black54),
backgroundColor: Color(0xfffee6dc),
centerTitle: true,
),
backgroundColor: Color(0xfffff6f7),
body: Obx(() => ListView.builder(
cacheExtent: 10,
addAutomaticKeepAlives: true,
physics: BouncingScrollPhysics(),
//physics : NeverScrollableScrollPhysics(),
itemCount: testController.itemsList.length,
itemExtent: 120,
itemBuilder: (context, index) {
print("item build");
return InkWell(
onLongPress:() {
testController.updateItem(index);
},
onDoubleTap: () {
testController.addItem();
},
child: Obx(()=> TestInternalWidget(testController.itemsList[index].a))
);
})
)
)
);
}
}
And last this is my test widget (testwidget.dart) used in list:
import 'package:flutter/material.dart';
class TestInternalWidget extends StatelessWidget {
String text= "";
TestInternalWidget(this.text);
#override
Widget build(BuildContext context) {
print("internal widget build");
return Container(
child:Text(text),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: Color(0xffE4CFC6)
)
);
}
}
IN GetX, the reactive variable reflects the changes in real-time if updated the item is itself.
But, if you just want to update a single attribute in the item, you have to refresh the reactive variable.
class TestController extends GetxController {
var itemsList = <item>[].obs;
#override
void onInit() {
print("init controller");
fetchItems();
super.onInit();
}
updateItem(int index){
print("UPDATING");
itemsList[index].a = "xxxxx";
itemsList[index].b = "XXXXX";
itemsList.refresh();
}
void fetchItems() async{
print("fetching items");
var item1 = item("aaaaa", "AAAAA");
var item2 = item("bbbbb", "BBBBB");
itemsList.add(item1);
itemsList.add(item2);
}
addItem()
{
print("ADDING");
var newItem = item("ccccc", "CCCCCC");
itemsList.add(newItem);
}
}

Flutter: Lazy Loading with low amount of data

I try to use lazy load to show the order of the customer by using the ScrollController.
Of course, the new user has a low number of orders and those items are not enough to take up the entire screen. So the ScrollController doesn't work. What I can do?
This code will show a basic lazy load. You can change the _initialItemsLength to a low value like 1 to see this issue.
You can try this at api.flutter.dev
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late List myList;
ScrollController _scrollController = ScrollController();
int _initialItemsLength = 10, _currentMax = 10;
#override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => "Item : ${i + 1}");
_scrollController.addListener(() {
print("scrolling: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
_getMoreData() {
print("load more: ${myList.length}");
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add("Item : ${i + 1}");
}
_currentMax = _currentMax + 10;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
}
First, start _initialItemsLength with 10. The scroller will be available and you will see it in the console. After that, change _initialItemsLength to 1. The console will be blank.
scroll listener will be triggered only if user try to scroll
as an option you need to check this condition _scrollController.position.pixels == _scrollController.position.maxScrollExtent after build method executed and each time when user scroll to bottom
just change a bit initState and _getMoreData methods
#override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() => _checkIsMaxScroll());
WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll());
}
void _checkIsMaxScroll() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_getMoreData();
}
}
_getMoreData() {
print('load more: ${myList.length}');
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add('Item : ${i + 1}');
}
_currentMax = _currentMax + 10;
setState(() => WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll()));
}
You can set your ListView with physics: AlwaysScrollableScrollPhysics(), and thus it will be scrollable even when the items are not too many. This will lead the listener to be triggered.
Key code part:
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
The point is 'Find some parameter that can tell whether scroll is enabled or not. If not just load more until the scroll is enabled. Then use a basic step for a lazy load like the code in my question.'
After I find this parameter on google, I don't find this. But I try to check any parameter as possible. _scrollController.any until I found this.
For someone who faces this issue like me.
You can detect the scroll is enabled by using _scrollController.position.maxScrollExtent == 0 with using some delay before that.
This is my code. You can see it works step by step in the console.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PageStackoverflow72734370 extends StatefulWidget {
const PageStackoverflow72734370({Key? key}) : super(key: key);
#override
State<PageStackoverflow72734370> createState() => _PageStackoverflow72734370State();
}
class _PageStackoverflow72734370State extends State<PageStackoverflow72734370> {
late final List myList;
final ScrollController _scrollController = ScrollController();
final int _initialItemsLength = 1;
bool isScrollEnable = false, isLoading = false;
#override
void initState() {
super.initState();
print("\ninitState work!");
print("_initialItemsLength: $_initialItemsLength");
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() {
print("\nListener work!");
print("position: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) _getData();
});
_helper();
}
Future _helper() async {
print("\nhelper work!");
while (!isScrollEnable) {
print("\nwhile loop work!");
await Future.delayed(Duration.zero); //Prevent errors from looping quickly.
try {
print("maxScroll: ${_scrollController.position.maxScrollExtent}");
isScrollEnable = 0 != _scrollController.position.maxScrollExtent;
print("isScrollEnable: $isScrollEnable");
if (!isScrollEnable) _getData();
} catch (e) {
print(e);
}
}
print("\nwhile loop break!");
}
void _getData() {
print("\n_getData work!");
if (isLoading) return;
isLoading = true;
int i = myList.length;
int j = myList.length + 1;
for (i; i < j; i++) {
myList.add("Item : ${i + 1}");
}
print("myList.length: ${myList.length}");
isLoading = false;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return const CupertinoActivityIndicator();
}
return ListTile(title: Text(myList[i]));
},
itemCount: myList.length + 1,
),
);
}
}
You can test in my test. You can change the initial and incremental values at ?initial=10&incremental=1.
I know, this case is rare. Most applications show more data widget height than the height of the screen or the data fetching 2 turns that enough for making these data widget height than the height of the screen. But I put these data widgets in the wrap for users that use the desktop app. So, I need it.

Flutter provider profile picture not updating

I am building a method that the user can select a prefered profile picture to show arround the app, using provider package. I used shared_preferences to save the profile picture preferences on locally as a int value. And it worked, means I can save the profile picture to local system. But the problem is, the provider package completely became useless in this case, because I have to convert the widget to statefull and call the setState method when ever I insert a profilePicture widget inside the widget tree. And even the profilePicture widget in the HomeScreen not updating this way. I want to know how can I use the provider package for this issue instead of using statefulWidgets.
watch the Gif or video
This is the Provider class I created:
class ProfilePicProvider with ChangeNotifier {
ProfilePicPref profilePicPreferences = ProfilePicPref();
int _svgNumber = 1;
int get svgNumber => _svgNumber;
set svgNumber(int value) {
_svgNumber = value;
profilePicPreferences.setProfilePic(value);
notifyListeners();
}
void changePic(int val) {
_svgNumber = val;
profilePicPreferences.setProfilePic(val);
notifyListeners();
}
}
This is the sharedPreferences class
class ProfilePicPref {
static const PRO_PIC_STS = 'PROFILESTATUS';
setProfilePic(int svgNo) async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
profilePref.setInt(PRO_PIC_STS, svgNo);
}
Future<int> getProfilePicture() async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
return profilePref.getInt(PRO_PIC_STS) ?? 1;
}
}
This is the image selection screen and save that data to sharedPreferences class
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
This is the HomeScreen which is not updating the profile image whether it is statefull or stateless
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final proPicProvider = Provider.of<ProfilePicProvider>(context);
return Scaffold(
body:
Column(
children: [
Row(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proPicProvider.svgNumber}.svg'),
],
),
],
),
);
}
}
example:
I have to convert the widget to statefull and call setState method to get the current profile picture from sharedPreferences. You may find this screen from the GIF I provided.
class Progress extends StatefulWidget {
const Progress({Key? key}) : super(key: key);
#override
State<Progress> createState() => _ProgressState();
}
class _ProgressState extends State<Progress> {
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SizedBox(
height: 130.0,
width: 130.0,
child: SvgPicture.asset(
'assets/svg/${proProvider.svgNumber}.svg'),
),
),
);
}
}
The problem is in _SelectProfilePictureState when you create new instance of your ChangeNotifier:
ProfilePicProvider proProvider = ProfilePicProvider();. It means you are not using the provider available across the context but creating new one every time. So when the value of your provider changed it has effect only inside _SelectProfilePictureState. Instead of creating new instance you must call it always using the context:
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
// [removed] ProfilePicProvider proProvider = ProfilePicProvider();
//removed
/*void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}*/
#override
Widget build(BuildContext context) {
//use provider from the context
final proProvider = Provider.of<ProfilePicProvider>(context,listen:true);
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
If you enter the application you may want send initially selected image to your provider:
Add parameter to the constructor of ProfilePicProvider:
ProfilePicProvider(SharedPreferences prefs): _svgNumber = prefs.getInt(ProfilePicPref.PRO_PIC_STS) ?? 1;
In main.dart:
Future<void> main()async{
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
runApp(
MultiProvider(
providers:[
ChangeNotifierProvider( create:(_) => ProfilePicProvider(prefs))
],
child: yourtopWidget
)
);
}

Implement setstat and bind variables

I'm trying to get and display 2 variable values from another dart file, ("int myId" and "String myMenu") , these variables are updated with every "onTap" widget, my code works, but only if i do a "hot reload", i think that i need to put a "setstate" somewhere, but i'm having difficulty to implement it.
I think the problem is there, my widget text returns "null" to me, but if I hit the menu button and do a "hot reload", it's ok.
displayText.dart
import 'package:flutter/material.dart';
import './menu.dart';
class display extends StatefulWidget {
int myId;
String myMenu;
display(this.myId, this.myMenu);
#override
_displayState createState() => _displayState();
}
class _displayState extends State<display> {
Future myVarUsed() async {
//Each press on the button return the value
setState(() {
print('myIdDsiplay: ${widget.myId}'); // null
print('myMenuDisplay : ${widget.myMenu}'); // null
});
}
#override
void initState() {
super.initState();
myVarUsed();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
This file contains the menu inside a scrollbar, each button return the ID and the name (of the button) and store it in 2 variable ("int myId" and "String myMenu") that i want to pass.
menu.dart
import 'package:flutter/material.dart';
import './mylist.dart';
import './displayText.dart';
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetector_Ontap(index),
);
},
),
);
}
GestureDetector gestureDetector_Ontap(int index) {
return GestureDetector(
onTap: () {
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
This file contains my list and his class
mylist.dart
class listModel {
int id;
String menuObj;
listModel(this.id, this.menuObj);
}
List listdata = [
listModel(0, 'Menu01'),
listModel(1, 'Menu02'),
listModel(2, 'Menu03'),
listModel(3, 'Menu04'),
listModel(4, 'Menu05')
];
And the container
main.dart
import 'package:flutter/material.dart';
import './menu.dart';
import './displayText.dart';
import './mylist.dart';
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
Menu(),
display(Menu.myId, Menu.myMenu),
],
),
),
),
);
}
}
The problem
You're defining Menu this way:
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
When your app starts, myId and myMenu are uninitialized variables, therefore they're implicitely set to null.
Inside _MyHomePageState, you call
display(Menu.myId, Menu.myMenu)
Since you haven't initialized Menu.myId and Menu.myMenu yet, they're still null.
When you tap the GestureDetector, you initialize Menu.myId and Menu.myMenu this way:
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
Now, Menu.myId and Menu.myMenu are defined to non-null values. However, this will not update the Container's display(Menu.myId, Menu.myMenu), so they'll still be null, you need to update it by yourself.
The solution
I've added comments through the code, pointing a better approach:
import 'package:flutter/material.dart';
// Avoid displaying the warning "Name types using UpperCamelCase."
class Display extends StatefulWidget {
// Make these fields final and the constructor const
final int myId;
final String myMenu;
const Display(this.myId, this.myMenu);
#override
_DisplayState createState() => _DisplayState();
}
// Avoid displaying the warning "Name types using UpperCamelCase."
class _DisplayState extends State<Display> {
// You don't need this Future nor this initState
//
// Future myVarUsed() async {
// setState(() {
// print('myIdDsiplay: ${widget.myId}'); // null
// print('myMenuDisplay : ${widget.myMenu}'); // null
// });
// }
//
// #override
// void initState() {
// super.initState();
// myVarUsed();
// }
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
class Menu extends StatefulWidget {
// Avoid using mutable static fields
// static int myId;
// static String myMenu;
// To simplify, you can add a onChanged callback to
// be triggered whenever you change `myId` and `myMenu`
final void Function(int myId, String myMenu) onChanged;
const Menu({this.onChanged});
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetectorOntap(index),
);
},
),
);
}
// Avoid displaying the warning "Name non-constant identifiers using lowerCamelCase."
GestureDetector gestureDetectorOntap(int index) {
return GestureDetector(
onTap: () {
// Make these local variables
int myId = listdata[index].id;
String myMenu = listdata[index].menuObj;
// Call the `onChanged` callback
widget.onChanged(myId, myMenu);
// This widget is being thrown away
// display(Menu.myId, Menu.myMenu);
print('myIDMenu $myId');
print('myMenuMenu $myMenu');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
// Avoid the warning "Name types using UpperCamelCase."
class ListModel {
// You can make these fields final and the constructor const
final int id;
final String menuObj;
const ListModel(this.id, this.menuObj);
}
// You can make this list const to avoid modifying it unintentionally later
const List<ListModel> listdata = [
ListModel(0, 'Menu01'),
ListModel(1, 'Menu02'),
ListModel(2, 'Menu03'),
ListModel(3, 'Menu04'),
ListModel(4, 'Menu05')
];
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// Create fields to store the current `myId` and current `myMenu`
int myId;
String myMenu;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
// Add the `onChanged` callback here, updating this widget state
Menu(
onChanged: (newMyId, newMyMenu) {
setState(() {
myId = newMyId;
myMenu = newMyMenu;
});
}
),
// Access the current values here
Display(myId, myMenu),
],
),
),
),
);
}
}

[FLUTTER]: Programmatically change tabs in the CustomNavigator from SecondScreen to FirstScreen

I'm currently making an app with bottom navigator. And I have troubles with navigating from SecondScreen to the FirstScreen, programmatically, inside the SecondScreen file. But I have no idea how to do it. Because I can't have the access to the CustomNavigatorState part of the CustomNavigator class.
My main.dart file:
import 'package:flutter/material.dart';
import './screens/custom_navigator.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Name',
home: Scaffold(
body: CustomNavigator(),
),
);
}
}
My custom_navigator.dart file, which includes CustomNavigator class and _CustomNavigatorState class:
import 'package:flutter/material.dart';
import './first_second.dart';
import './second_screen.dart';
import './third_screen.dart';
import '../widgets/tab_navigator.dart';
class CustomNavigator extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CustomNavigatorState();
}
class _CustomNavigatorState extends State<CustomNavigator> {
String _currentScreen = FirstScreen.route;
List<String> _screenKeys = [
FirstScreen.route,
SecondScreen.route,
ThirdScreen.route,
];
Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
FirstScreen.route: GlobalKey<NavigatorState>(),
SecondScreen.route: GlobalKey<NavigatorState>(),
ThirdScreen.route: GlobalKey<NavigatorState>(),
};
int _selectedIndex = 0;
void changeTab(String tabItem, int index) {
_selectedTab(tabItem, index);
}
void _selectedTab(String tabItem, int index) {
if (tabItem == _currentScreen) {
_navigatorKeys[tabItem].currentState.popUntil((route) => route.isFirst);
} else {
setState(() {
_currentScreen = _screenKeys[index];
_selectedIndex = index;
});
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _navigatorKeys[_currentScreen].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentScreen != FirstScreen.route) {
_selectedTab(FirstScreen.route, 1);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
resizeToAvoidBottomPadding: true,
body: Stack(
children: [
_buildOffstageNavigator(FirstScreen.route),
_buildOffstageNavigator(ScreenScreen.route),
_buildOffstageNavigator(ThirdScreen.route),
],
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
_selectedTab(_screenKeys[index], index);
},
currentIndex: _selectedIndex,
items: [
BottomNavigationBarItem(
label: 'First',
),
BottomNavigationBarItem(
label: 'Second',
),
BottomNavigationBarItem(
label: 'Third',
),
],
),
),
);
}
Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentScreen != tabItem,
child: TabNavigator(
navigatorKey: _navigatorKeys[tabItem],
tabItem: tabItem,
),
);
}
}
TabNavigator class, where the screens added.
import 'package:flutter/material.dart';
import '../screens/first_screen.dart';
import '../screens/second_screen.dart';
import '../screens/third_screen.dart';
class TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final String tabItem;
const TabNavigator({
Key key,
this.navigatorKey,
this.tabItem,
}) : super(key: key);
#override
Widget build(BuildContext context) {
Widget child;
if (tabItem == FirstScreen.route) {
child = FirstScreen();
} else if (tabItem == SecondScreen.route) {
child = SecondScreen();
} else if (tabItem == ThirdScreen.route) {
child = ThirdScreen();
}
return Navigator(
key: navigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => child,
);
},
);
}
}
I tried to navigate with Navigator.push and Navigator.pushNamed, but it navigates inside SecondScreen without changing the BottomNavigationTabBars.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => SecondScreen(),
),
);
Navigator.of(context).pushNamed(SecondScreen.route);
Also I can't use Provider, because I don't have access to _CustomNavigatorState class. Could anybody offer me any decision of the problem. Thanks.
I notice you have the nested Scaffolds, it's probably better to move your BottomNavigationBar to the outer Scaffold so you only have one Scaffold in your app. For the body of the outter Scaffold you will have your Stack
Regarding the navigator issues. The body of your app in a Stack with three offstage widgets. Only one of the widgets is visible at a time. When changing between each Offstage widget you don't actually navigate to it, all you have to do is change your _currentScreen to which ever you would like. So if you're on page one and would like to "push" to page 2 then have something like
onPressed: () {
SetState(() {
_currentScreen = FirstScreen.route;
}
}
Then when your body rebuilds from the setState it will set the FirstScreen to be onstage and all other screens to be offstage. Showing you the FirstScreen.