Flutter: State management error with go_router and riverpod - flutter

I realise a Flutter app and I'm a really beginner. I'm using Riverpod for the state management and go_router for the routing. I try to implement a navbar visible only if you are logged. But I think I have a state management issue: when I press a navbar button, nothing happened (no console error neither) but If I logout and login or if I modify my code and save, my Emulator go to the right page.
I try to wrap my pages in a bigger Scaffold, to persist the AppBar and NavBar.
Here is my main.dart:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
// This widgets is the root of your application.
#override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
title: 'Ludocal 2',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
debugShowCheckedModeBanner: false,
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
}
}
My router:
List<GoRoute> get _routes => [
GoRoute(
name: 'login',
builder: (context, state) => const LoginScreen(),
path: '/login'),
GoRoute(
path: '/:screenName(home|game|event|profile)',
builder: (BuildContext context, GoRouterState state) {
final String screenName = state.params['screenName']!;
return LoggedScreen(screenName: screenName);
})
];
My logged_screen.dart wrapping my other screens:
class LoggedScreen extends HookConsumerWidget {
const LoggedScreen({super.key, required this.screenName});
final String screenName;
#override
Widget build(BuildContext context, WidgetRef ref) {
Future.delayed(Duration.zero, () {
switch (ref.read(indexProvider.state).state) {
case 0:
context.go('/home');
break;
case 1:
context.go('/game');
break;
case 2:
context.go('/event');
break;
case 3:
context.go('/profile');
break;
}
});
return Scaffold(
appBar: AppBar(
title: Text("Ludocal 2"),
backgroundColor: Colors.deepOrangeAccent,
actions: [
TextButton.icon(
icon: Icon(
Icons.logout_rounded,
color: Colors.white,
),
label: Text('', style: TextStyle(color: Colors.white)),
onPressed: () async {
ref.read(loginControllerProvider.notifier).signOut();
},
),
]),
body: BodyTab(screenName: screenName),
bottomNavigationBar: const BottomTab(),
);
}
}
class BodyTab extends ConsumerWidget {
const BodyTab({super.key, required this.screenName});
final String screenName;
#override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
children: [
Expanded(
child: screenName == 'home'
? const HomeScreen()
: screenName == 'game'
? const GameScreen()
: screenName == 'event'
? const EventScreen()
: const ProfileScreen()),
],
);
}
}
class BottomTab extends ConsumerWidget {
const BottomTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
return BottomNavigationBar(
currentIndex: ref.read(indexProvider.state).state,
onTap: (int index) => ref.read(indexProvider.state).state = index,
backgroundColor: Colors.deepOrangeAccent,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.emoji_emotions),
label: 'Game',
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: 'Event',
),
],
);
}
}
final indexProvider = StateProvider<int>((ref) {
return 0;
});
The login_controller.dart:
class LoginController extends StateNotifier<LoginState> {
LoginController(this.ref) : super(const LoginStateInitial());
final Ref ref;
void login(String email, String password) async {
state = const LoginStateLoading();
try {
await ref.read(authRepositoryProvider).signInWithEmailAndPassword(
email,
password,
);
state = const LoginStateSuccess();
} catch (e) {
state = LoginStateError(e.toString());
}
}
void signOut() async {
await ref.read(authRepositoryProvider).signOut();
state = const LoginStateInitial();
}
}
final loginControllerProvider =
StateNotifierProvider<LoginController, LoginState>((ref) {
return LoginController(ref);
});
Appreciate if someone can advise. Thank you in advance!

For navigation you need to use listen like below.
ref.listen(indexProvider, (previous, next) {
switch (next) {
case 0:
context.go('/home');
break;
case 1:
context.go('/game');
break;
case 2:
context.go('/event');
break;
case 3:
context.go('/profile');
break;
}
});

Related

How to reflect the value from FutureProvider when certain UI onPressed?

I'm very new about Flutter and the library reiver_pod.
I want show Text("Hello World") on screen, only when floatingActionButton pressed with using FutureProvider but it's always shown even though the button has never been pressed ? How it come and how can I Fix it ?
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FutureProvider',
theme: ThemeData(
textTheme: const TextTheme(bodyText2: TextStyle(fontSize: 50)),
),
home: HomePage(),
);
}
}
final futureProvider = FutureProvider<dynamic>((ref) async {
await Future.delayed(const Duration(seconds: 3));
return 'Hello World';
});
class HomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final asyncValue = ref.watch(futureProvider);
return Scaffold(
appBar: AppBar(title: const Text('TEST')),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
ref.refresh(futureProvider);
},
),
body: Center(
child: asyncValue.when(
error: (err, _) => Text(err.toString()),
loading: () => const CircularProgressIndicator(),
data: (data) {
print(data);
return Text(data.toString());//here
},
),
),
);
}
}
renewal code as follow:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:math';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FutureProvider',
theme: ThemeData(
textTheme: const TextTheme(bodyText2: TextStyle(fontSize: 50)),
),
home: HomePage(),
);
}
}
final StateProvider<bool> pressProvider = StateProvider((ref) => false);
final futureProvider = FutureProvider<dynamic>((ref) async {
var intValue = Random().nextInt(100);
await Future.delayed(const Duration(seconds: 1));
return intValue.toString();
});
class HomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('TEST')),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
ref.read(pressProvider.notifier).update((state) => true);
ref.refresh(futureProvider);
},
),
body: Center(
child: ref.watch(pressProvider)
? Consumer(
builder: (context, ref, child) {
final asyncValue = ref.watch(futureProvider);
return asyncValue.when(
error: (err, _) => Text(err.toString()),
loading: () => const CircularProgressIndicator(),
data: (data) {
return Text(data.toString()); //here
},
);
},
)
: null),
);
}
}
You can use a bool to handle tap event like, FutureProvider will handle the UI update case.
class HomePage extends ConsumerWidget {
bool isPressed = false;
#override
Widget build(BuildContext context, WidgetRef ref) {
final asyncValue = ref.watch(futureProvider);
return Scaffold(
appBar: AppBar(title: const Text('TEST')),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
isPressed = true;
ref.refresh(futureProvider);
},
),
body: Center(
child: isPressed
? asyncValue.when(
error: (err, _) => Text(err.toString()),
loading: () => const CircularProgressIndicator(),
data: (data) {
print(data);
return Text(data.toString()); //here
},
)
: null,
),
);
}
}

Flutter Getx How to navigate to other page in StreamBuilder

I am using firebase_auth to signup and login.
I am facing a problem with Streambuilder.
I would like to show page depends on User Logged in or not. It seems working fine.
But, the problem is that I can't use Get.off('/app'); in StreamBuilder and FutureBuilder.
if I can't use Getx.off('/app'); user can get back just pressing the back button, and
I would like to avoid this, so I am trying to use Get.off page route.
But, as vs code shows that FutureBuilder and StreamBuilder's builder return Widget,
and I have no idea how to code.
Any suggestion for this matter?
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Karrot Market Clone',
theme: ThemeData(
primaryColor: Colors.black,
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
appBarTheme: AppBarTheme(
color: Colors.white,
),
),
initialBinding: InitBinding(),
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => BridgeFirebase(),
),
GetPage(
name: '/bridge_page',
page: () => BridgePage(),
),
GetPage(
name: '/app',
page: () => App(),
transition: Transition.rightToLeft,
),
GetPage(
name: '/start',
page: () => Start(),
),
GetPage(
name: '/login',
page: () => Login(),
transition: Transition.rightToLeft,
),
GetPage(
name: '/signup',
page: () => SignUp(),
transition: Transition.rightToLeft,
),
],
);
}
}
class BridgeFirebase extends StatelessWidget {
const BridgeFirebase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Firebase.initializeApp(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Firebase load fail'));
}
if (snapshot.connectionState == ConnectionState.done) {
return BridgePage();
}
return Center(
child: CircularProgressIndicator(
color: ColorsKM.primary,
),
);
},
);
}
}
class BridgePage extends StatelessWidget {
const BridgePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Splash();
}
if (snapshot.hasData) {
return App();
} else {
return Start();
}
},
);
}
}
// app.dart
class App extends GetView<AppController> {
const App({Key? key}) : super(key: key);
Widget _bodyWidget() {
switch (RouteName.values[controller.currentIndex.value]) {
case RouteName.HOME:
return Home();
break;
case RouteName.MYLOCAL:
return MyLocal();
break;
case RouteName.NEARBY:
return Nearby();
break;
case RouteName.CHATS:
return Chats();
break;
case RouteName.MYKARROT:
return MyKarrot();
break;
}
return Container();
}
BottomNavigationBarItem _bottomNavigationBarItem(
String iconName, String label) {
return BottomNavigationBarItem(
icon: Padding(
padding: EdgeInsets.only(bottom: 5.0),
child: SvgPicture.asset('assets/svg/${iconName}_off.svg', width: 22),
),
activeIcon: Padding(
padding: EdgeInsets.only(bottom: 5.0),
child: SvgPicture.asset('assets/svg/${iconName}_on.svg', width: 22),
),
label: label,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(
() {
return _bodyWidget();
},
),
bottomNavigationBar: Obx(
() => BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: controller.currentIndex.value,
selectedFontSize: 12.0,
showSelectedLabels: true,
selectedItemColor: Colors.black,
selectedLabelStyle: TextStyle(color: Colors.black),
onTap: controller.changePageIndex,
items: [
_bottomNavigationBarItem('home', 'home'),
_bottomNavigationBarItem('notes', 'neighbor'),
_bottomNavigationBarItem('location', 'nearby'),
_bottomNavigationBarItem('chat', 'chat'),
_bottomNavigationBarItem('user', 'my karrot'),
],
),
),
);
}
}
// app_controller.dart
enum RouteName {
HOME,
MYLOCAL,
NEARBY,
CHATS,
MYKARROT,
}
class AppController extends GetxService {
static AppController get to => Get.find();
late RxInt currentIndex = 0.obs;
void changePageIndex(int index) {
currentIndex(index);
}
}
Because the build method is not completed when you navigate in Streambuilder so you can delay some time to the build method complete then navigate. do this when you want to navigate in Streambuilder :
Future.delayed(Duration.zero).then((value) => Get.off('/app'));
or just navigate in your controller or bloc class.

How to reload page on indexedstack in Flutter

Currently i'm using indexedstack and bottomnavigationbar, there are two pages "HomePage" & "SearchPage". These two pages i put in a children inside indexedstack widget. Now the problem is
If i switch to Search Page or switch back to Home Page it does not reload the page. How to solve this issue using the current widget which is indexedstack widget.
Whenever i run the app it loads all the pages including Search page which is not a current page.
Below is the sample code.
main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: App(),
);
}
}
class App extends StatefulWidget {
#override
State<StatefulWidget> createState() => AppState();
}
class AppState extends State<App> {
static int currentTab = 0;
final List<TabItem> tabs = [
TabItem(
tabName: "Home",
icon: Icons.home_outlined,
page: HomePage(),
),
TabItem(
tabName: "Search",
icon: Icons.search,
page: SearchPage(),
),
];
AppState() {
tabs.asMap().forEach((index, details) {
details.setIndex(index);
});
}
void _selectTab(int index) {
if (index == currentTab) {
tabs[index].key.currentState!.popUntil((route) => route.isFirst);
} else {
setState(() => currentTab = index);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await tabs[currentTab].key.currentState!.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentTab != 0) {
_selectTab(0);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
body: IndexedStack(
index: currentTab,
children: tabs.map((e) => e.page).toList(),
),
bottomNavigationBar: BottomNavigation(
onSelectTab: _selectTab,
tabs: tabs,
),
),
);
}
}
TabItem
class TabItem {
final String tabName;
final IconData icon;
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
int _index = 0;
late Widget _page;
TabItem({
required this.tabName,
required this.icon,
required Widget page,
}) {
_page = page;
}
void setIndex(int i) {
_index = i;
}
int getIndex() => _index;
Widget get page {
return Visibility(
visible: _index == AppState.currentTab,
maintainState: true,
child: Navigator(
key: key,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (_) => _page,
);
},
),
);
}
}
BottomNavigation
class BottomNavigation extends StatelessWidget {
BottomNavigation({
required this.onSelectTab,
required this.tabs,
});
final ValueChanged<int> onSelectTab;
final List<TabItem> tabs;
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: tabs
.map(
(e) => _buildItem(
index: e.getIndex(),
icon: e.icon,
tabName: e.tabName,
),
)
.toList(),
onTap: (index) => onSelectTab(
index,
),
);
}
BottomNavigationBarItem _buildItem(
{required int index, required IconData icon, required String tabName}) {
return BottomNavigationBarItem(
icon: Icon(
icon,
color: _tabColor(index: index),
),
title: Text(
tabName,
style: TextStyle(
color: _tabColor(index: index),
fontSize: 12,
),
),
);
}
Color _tabColor({required int index}) {
return AppState.currentTab == index
? Colors.red
: Colors.black;
}
}

Get current route name of CupertinoTabView in Flutter?

I'm using CupertinoTabScaffold and CupertinoTabView to build navigation bottom bar in my App. For one CupertinoTabView I go to others pushed routes name, I would like to get the current name of a CupertinoTabView, but I get Null
I define the routes in main like that
CupertinoApp(
home: MyApp(),
title: 'machin',
routes: appRoutes,)
final appRoutes = {
'/pushedName': (context) => PushedName(),
};
MyApp class //
final GlobalKey<NavigatorState> profileTabNavKey =
GlobalKey<NavigatorState>();
CupertinoTabScaffold(
tabBar: CupertinoTabBar(
activeColor: Color(0xff077018),
border: Border.all(color: Color(0xffffffff)),
currentIndex: widget.currentIndex,
onTap: (index) {},
items: <BottomNavigationBarItem>[....],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: profileTabNavKey,
routes: appRoutes,
builder: (BuildContext context) =>
SettingsView());
break;
default:
return HomePage();
}
},
),
In the SettingsView I pushed a named route by using
Navigator.pushNamed(context, '/pushedName')
I tried to get the route name in the my app class by using
print(ModalRoute.of(profileTabNavKey.currentContext).settings.name);
nb: in the pushedName View i get it perfectly any help , thanks in advance
Just use the BuildContext from the build widget to get the ModalRoute data :
ModalRoute.of(context).settings.name
Working example :
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
theme: CupertinoTheme.of(context).copyWith(
brightness: Brightness.light,
),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Home',
icon: Icon(CupertinoIcons.home),
),
BottomNavigationBarItem(
label: 'Setting',
icon: Icon(CupertinoIcons.settings),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 1:
return CupertinoTabView(
routes: <String, WidgetBuilder>{
'/setting': (context) => SettingsPage(),
'/setting/2': (context) => SettingsPage(2),
'/setting/2/3': (context) => SettingsPage(3),
},
builder: (context) => SettingsPage(),
);
break;
default:
return Center(
child: Text('Home page'),
);
}
},
);
}
}
class SettingsPage extends StatelessWidget {
final int index;
SettingsPage([this.index = 1]);
#override
Widget build(BuildContext context) {
// here we go to get the current route name
print(ModalRoute.of(context).settings.name);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
middle: Text(index > 1 ? 'Settings page - $index' : 'Settings page'),
),
child: Center(
child: CupertinoButton.filled(
child: Text('Go'),
onPressed: () {
if (index == 1) {
Navigator.pushNamed(context, '/setting/2');
} else if (index == 2) {
Navigator.pushNamed(context, '/setting/2/3');
}
},
),
),
);
}
}
Go to Dartpad
I found a solution that doesn't sound like best practice, but it works!
Instead of using ModalRouter and other libraries like navigation_history_observer, I used Navigator.popUntil and blocked the popup from getting the current route from the argument and assigned it to a variable.
WillPopScope(
onWillPop: () async {
String currentRoute;
navigatorKeys[_tabController.index].currentState.popUntil((route) {
currentRoute = route.settings.name;
return true;
});
if (currentRoute == '/') {
return Future.value(false);
} else {
return !await navigatorKeys[_tabController.index]
.currentState
.maybePop();
}
},
// ...
);

How do I make BottomNavigationBar page transition with Flutter's onWillpop?

I am using BottomNavigationBar in Flutter Project.
This question is for line 30 "//TODO: back to the FirstTab".
When a user is in the SecondTab or ThirdTab and the BackButton in the Android device is pressed, I want the user to go to the FirstTab.
Now, in onWillpopScope, there is a process to pop when you can. It is used when the user is in NextPage.
Then, when the user is not in the FirstTab (SecondTab or ThirdTab) and not in the NextTab, I want to move him to the FirstTab in onWillpopScope. (I want to force the BottomNavigationBar to switch.)
How should I describe it? Please tell me.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<GlobalKey<NavigatorState>> navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
];
#override
Widget build(BuildContext context) {
int currentIndex = 0;
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab = await navigatorKeys[currentIndex].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentIndex != 0) {
//TODO: back to the FirstTab
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(label: 'Home', icon: Icon(Icons.home)),
BottomNavigationBarItem(label: 'Search', icon: Icon(Icons.search)),
BottomNavigationBarItem(label: 'Setting', icon: Icon(Icons.settings)),
],
onTap: (index) {
// back home only if not switching tab
if (currentIndex == index) {
switch (index) {
case 0:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 1:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 2:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
}
}
currentIndex = index;
},
currentIndex: currentIndex,
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
navigatorKey: navigatorKeys[index],
builder: (BuildContext context) {
switch (index) {
case 0:
return FirstTab();
case 1:
return SecondTab();
case 2:
return ThirdTab();
default:
return FirstTab();
}
},
);
},
),
),
);
}
}
class FirstTab extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('first page now'),
),
backgroundColor: Colors.red[200],
child: Center(
child: CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.of(context).push(CupertinoPageRoute(builder: (context) => NextPage()));
},
),
),
);
}
}
//Different color from Firsttab
class SecondTab extends StatelessWidget {}
//Different color from Firsttab
class ThirdTab extends StatelessWidget {}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('second page now'),
),
backgroundColor: Colors.white,
child: Center(
child: CupertinoButton(
child: const Text('Back'),
onPressed: () {
Navigator.of(context).pop();
},
),
),
);
}
}
You can try to see it docs.
I'll resume here. First you need to create a controller
final CupertinoTabController _controller = CupertinoTabController();
and add to your CupertinoTabScaffold like this
CupertinoTabScaffold(
...
controller: _controller,
)
in the end you change the page like this:
_controller.index = 0,
(this is how get and set work in Flutter)
first move
int currentIndex = 0;
to class member
next
//TODO: back to the FirstTab
setState((){
currentIndex = 0;
});