In Flutter, I want to make screens like with Fragment in android, in this my code i try to replace each screens into current screen like with Fragment.replecae in android, i used Hook and Provider and my code work fine when in click on buttons to switch between them but i can't implementing back stack, which means when i click on Back button on phone, my code should show latest screen which i stored into _backStack variable, each swtich between this screens i stored current screen index into the this variable.
how can i solve back from this stack in my sample code?
// Switch Between screens:
DashboardPage(), UserProfilePage(), SearchPage()
-------------> -------------> ------------->
// When back from stack:
DashboardPage(), UserProfilePage(), SearchPage()
Exit from application <-------------- <---------------- <-----------
i used Hook and i want to implementing this action with this library features
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:provider/provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MultiProvider(providers: [
Provider.value(value: StreamBackStackSupport()),
StreamProvider<homePages>(
create: (context) =>
Provider.of<StreamBackStackSupport>(context, listen: false)
.selectedPage,
)
], child: StartupApplication()));
}
class StartupApplication extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BackStack Support App',
home: MainBodyApp(),
);
}
}
class MainBodyApp extends HookWidget {
final List<Widget> _fragments = [
DashboardPage(),
UserProfilePage(),
SearchPage()
];
List<int> _backStack = [0];
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BackStack Screen'),
),
body: WillPopScope(
// ignore: missing_return
onWillPop: () {
customPop(context);
},
child: Container(
child: Column(
children: <Widget>[
Consumer<homePages>(
builder: (context, selectedPage, child) {
_currentIndex = selectedPage != null ? selectedPage.index : 0;
_backStack.add(_currentIndex);
return Expanded(child: _fragments[_currentIndex]);
},
),
Container(
width: double.infinity,
height: 50.0,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
color: Colors.indigo[400],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenDashboard),
child: Text('Dashboard'),
),
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenProfile),
child: Text('Profile'),
),
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenSearch),
child: Text('Search'),
),
],
),
),
],
),
),
),
);
}
void navigateBack(int index) {
useState(() => _currentIndex = index);
}
void customPop(BuildContext context) {
if (_backStack.length - 1 > 0) {
navigateBack(_backStack[_backStack.length - 1]);
} else {
_backStack.removeAt(_backStack.length - 1);
Provider.of<StreamBackStackSupport>(context, listen: false)
.switchBetweenPages(homePages.values[_backStack.length - 1]);
Navigator.pop(context);
}
}
}
class UserProfilePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenProfile ...'),
);
}
}
class DashboardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenDashboard ...'),
);
}
}
class SearchPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenSearch ...'),
);
}
}
enum homePages { screenDashboard, screenProfile, screenSearch }
class StreamBackStackSupport {
final StreamController<homePages> _homePages = StreamController<homePages>();
Stream<homePages> get selectedPage => _homePages.stream;
void switchBetweenPages(homePages selectedPage) {
_homePages.add(homePages.values[selectedPage.index]);
}
void close() {
_homePages.close();
}
}
TL;DR
The full code is at the end.
Use Navigator instead
You should approach this problem differently. I could present you with a solution that would work with your approach, however, I think that you should instead solve this by implementing a custom Navigator as this is a built-in solution in Flutter.
When you are using a Navigator, you do not need any of your stream-based management, i.e. you can remove StreamBackStackSupport entirely.
Now, you insert a Navigator widget where you had your Consumer before:
children: <Widget>[
Expanded(
child: Navigator(
...
),
),
Container(...), // Your bottom bar..
]
The navigator manages its routes using strings, which means that we will need to have a way to convert your enum (which I renamed to Page) to Strings. We can use describeEnum for that and put that into an extension:
enum Page { screenDashboard, screenProfile, screenSearch }
extension on Page {
String get route => describeEnum(this);
}
Now, you can get the string representation of a page using e.g. Page.screenDashboard.route.
Furthermore, you want to map your actual pages to your fragment widgets, which you can do like this:
class MainBodyApp extends HookWidget {
final Map<Page, Widget> _fragments = {
Page.screenDashboard: DashboardPage(),
Page.screenProfile: UserProfilePage(),
Page.screenSearch: SearchPage(),
};
...
To access the Navigator, we need to have a GlobalKey. Usually we would have a StatefulWidget and manage the GlobalKey like that. Since you want to use flutter_hooks, I opted to use a GlobalObjectKey instead:
#override
Widget build(BuildContext context) {
final navigatorKey = GlobalObjectKey<NavigatorState>(context);
...
Now, you can use navigatorKey.currentState anywhere in your widget to access this custom navigator. The full Navigator setup looks like this:
Navigator(
key: navigatorKey,
initialRoute: Page.screenDashboard.route,
onGenerateRoute: (settings) {
final pageName = settings.name;
final page = _fragments.keys.firstWhere((element) => describeEnum(element) == pageName);
return MaterialPageRoute(settings: settings, builder: (context) => _fragments[page]);
},
)
As you can see, we pass the navigatorKey created before and define an initialRoute, making use of the route extension we created. In onGenerateRoute, we find the Page enum entry corresponding to the route name (a String) and then return a MaterialPageRoute with the appropriate _fragments entry.
To push a new route, you simply use the navigatorKey and pushNamed:
onPressed: () => navigatorKey.currentState.pushNamed(Page.screenDashboard.route),
Back button
We also need to customly call pop on our custom navigator. For this purpose, a WillPopScope is needed:
WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState.canPop()) {
navigatorKey.currentState.pop();
return false;
}
return true;
},
child: ..,
)
Access the custom navigator inside of the nested pages
In any page that is passed to onGenerateRoute, i.e. in any of your "fragments", you can just call Navigator.of(context) instead of using the global key. This is possible because these routes are children of the custom navigator and thus, the BuildContext contains that custom navigator.
For example:
// In SearchPage
Navigator.of(context).pushNamed(Page.screenProfile.route);
Default navigator
You might be wondering how you can get access to the MaterialApp root navigator now, e.g. to push a new full screen route. You can use findRootAncestorStateOfType for that:
context.findRootAncestorStateOfType<NavigatorState>().push(..);
or simply
Navigator.of(context, rootNavigator: true).push(..);
Here is the full code:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(StartupApplication());
}
enum Page { screenDashboard, screenProfile, screenSearch }
extension on Page {
String get route => describeEnum(this);
}
class StartupApplication extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BackStack Support App',
home: MainBodyApp(),
);
}
}
class MainBodyApp extends HookWidget {
final Map<Page, Widget> _fragments = {
Page.screenDashboard: DashboardPage(),
Page.screenProfile: UserProfilePage(),
Page.screenSearch: SearchPage(),
};
#override
Widget build(BuildContext context) {
final navigatorKey = GlobalObjectKey<NavigatorState>(context);
return WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState.canPop()) {
navigatorKey.currentState.pop();
return false;
}
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text('BackStack Screen'),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Navigator(
key: navigatorKey,
initialRoute: Page.screenDashboard.route,
onGenerateRoute: (settings) {
final pageName = settings.name;
final page = _fragments.keys.firstWhere(
(element) => describeEnum(element) == pageName);
return MaterialPageRoute(settings: settings,
builder: (context) => _fragments[page]);
},
),
),
Container(
width: double.infinity,
height: 50.0,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
color: Colors.indigo[400],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenDashboard.route),
child: Text('Dashboard'),
),
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenProfile.route),
child: Text('Profile'),
),
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenSearch.route),
child: Text('Search'),
),
],
),
),
],
),
),
),
);
}
}
class UserProfilePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenProfile ...'),
);
}
}
class DashboardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenDashboard ...'),
);
}
}
class SearchPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenSearch ...'),
);
}
}
Related
I want to show an overlay on the whole app so I tried to insert an overlay entry on the context of MaterialApp (root widget) but the problem is I'm getting the null value on invoking the following method :
Overlay.of(context);
GetMaterialApp.router(
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
scaffoldMessengerKey: Keys.scaffold,
scrollBehavior: MyCustomScrollBehavior(),
routeInformationParser: WebRoutes.goRouter.routeInformationParser,
routerDelegate: WebRoutes.goRouter.routerDelegate,
routeInformationProvider: WebRoutes.goRouter.routeInformationProvider,
builder: (context, child) {
WidgetsBinding.instance.addPostFrameCallback((_){
addOverlay(context);
});
return child;
}
void addOverlay(BuildContext context) {
print(Overlay.of(context));
return Overlay.of(context)?.insert(OverlayEntry(
builder: (context) {
return SomeWidget();
},
));
}
Is there any way to get the state of overlay using the context of this root widget as I want to show the overlay globally.
Thanks alot, I really appreciate that If someone helps me.
MaterialApp(
navigatorKey: getIt.get<NavigatorService>().navigatorKey,
theme: AppTheme.defaultTheme,
initialRoute: AppRoutes.splashScreen,
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
top: 15,
child: Container(
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
child: const Center(child: Text("HI I AM AN OVERLAY")),
),
),
],
),
);
},
onGenerateRoute: AppRoutes.onGenerateRoute,
),
You can achieve that by create a class responsible to display/remove the overlay, this class need receive a BuildContext when creating to be able to create an instance of Overlay.
Basically what you need to do are:
Create a class OverlayScreen that build the OverlayState && OverlayEntry (in this case the OverylayEntry will be a list of OverlayEntry since we might have more than one Overlay on the screen so we can remove all of them at once).
Create an instance of this class earlier in your app (e.g MyApp). In your case you'll need to call this inside Material.router...builder param.
Access this overlayScreen in your HomePage to display|removeAll overlays
Lets create our OverlayScreen
import 'package:flutter/material.dart';
class OverlayScreen {
/// Create an Overlay on the screen
/// Declared [overlayEntrys] as List<OverlayEntry> because we might have
/// more than one Overlay on the screen, so we keep it on a list and remove all at once
BuildContext _context;
OverlayState? overlayState;
List<OverlayEntry>? overlayEntrys;
void closeAll() {
for (final overlay in overlayEntrys ?? <OverlayEntry>[]) {
overlay.remove();
}
overlayEntrys?.clear();
}
void show() {
overlayEntrys?.add(
OverlayEntry(
builder: (context) {
return _buildOverlayWidget();
},
),
);
overlayState?.insert(overlayEntrys!.last);
}
OverlayScreen._create(this._context) {
overlayState = Overlay.of(_context);
overlayEntrys = [];
}
factory OverlayScreen.of(BuildContext context) {
return OverlayScreen._create(context);
}
Widget _buildOverlayWidget() {
return Positioned(
top: 20,
left: 20,
right: 20,
child: Container(
width: 300,
color: Colors.black,
height: 300,
child: const Text("MY CHAT"),
),
);
}
}
Now lets create an instance on MyApp
// Need to have it global to be able to access everywhere
OverlayScreen? overlayScreen;
void main() {
runApp(
const MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
// Create an instance of `OverlayScreen` to be accessed globally
overlayScreen = OverlayScreen.of(context);
return child ?? const SizedBox();
},
),
],
);
},
);
}
}
To finalise lets create our HomePage and access our overlayScreen there there
import 'package:flutter/material.dart';
import 'package:overlay_all_app/src/overlay_screen.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
// Create an instance of OverlayScreen
final overlayScreen = OverlayScreen.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () {
// display the overlay
overlayScreen.show();
},
child: const Text('Display Overlay'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// Call your next screen here
},
child: const Text('Go to next page'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// removed all overlays on the screen
overlayScreen.closeAll();
},
child: const Text('Close Overlay'),
),
],
),
),
);
}
}
That's it. You can use this class OverlayScreen to show/removeAll wherever you want.
I created a PR with sample code, check it out https://github.com/antonio-nicolau/flutter-working-with-overlay
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:go_router/go_router.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(App2());
}
class App2 extends StatelessWidget {
App2({super.key});
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OverlayWrapper(),
),
],
);
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
routeInformationProvider: _router.routeInformationProvider,
);
}
}
class OverlayWrapper extends StatefulWidget {
const OverlayWrapper({Key? key}) : super(key: key);
#override
State<OverlayWrapper> createState() => _OverlayWrapperState();
}
class _OverlayWrapperState extends State<OverlayWrapper> {
#override
void initState() {
super.initState();
}
showOverLay() {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Container(
color: Colors.red,
child: const Text('data'),
),
);
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showOverLay();
},
child: const Text(
'ShowOverlay',
style: TextStyle(),
),
),
),
);
}
}
I'm trying to send a list from one page to another page in Flutter. I was able to see my list in Text first, but I couldn't add it to a Wheel of Fortune using flutter_fortune_wheel 1.2.I couldn't understand if the problem is in the package or the code I wrote, can you help me?
Main
import 'package:flutter/material.dart';
import 'MainScreen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainScreen(),
);
}
}
MainScreen
import 'package:flutter/material.dart';
import 'SpinningWheelScreen.dart';
class MainScreen extends StatefulWidget {
#override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
TextEditingController t1 = TextEditingController();
void add() {
setState(() {
myList.add(t1.text);
t1.clear();
});
}
final myList = <String>[];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
centerTitle: true,
),
body: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Entering Page"),
TextFormField(
controller: t1,
),
ElevatedButton(onPressed: add, child: Text('Add')),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SpinningWheelScreen(
inputs: [myList],
)),
);
},
child: Text('Next'))
],
),
),
),
);
}
}
SpinningWheelScreen
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_fortune_wheel/flutter_fortune_wheel.dart';
class SpinningWheelScreen extends StatefulWidget {
var inputs = [];
SpinningWheelScreen({required this.inputs});
#override
State<SpinningWheelScreen> createState() => _SpinningWheelScreenState();
}
class _SpinningWheelScreenState extends State<SpinningWheelScreen> {
StreamController<int> selected = StreamController<int>();
void dispose() {
selected.close;
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WheelSpin App'),
centerTitle: true,
),
body: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
setState(() {
selected.add(Fortune.randomInt(0, widget.inputs.length));
});
},
),
Expanded(
child: FortuneWheel(
animateFirst: false,
selected: selected.stream,
items: [
for (var it in widget.inputs) FortuneItem(child: Text(it)),
],
),
),
Text(widget.inputs.toString()),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back')),
],
),
),
),
);
}
}
I would like to route myList to another screen
The error occurs because you are passing a List<List> to your SpinningWheelScreen
MaterialPageRoute(
builder: (context) => SpinningWheelScreen(
inputs: [myList], // myList is already a List
),
),
To fix your error do the following:
MaterialPageRoute(
builder: (context) => SpinningWheelScreen(
inputs: myList, // Remove the brackets
),
),
Now you are passing just a List to SpinningWheelScreen.
You have to add one more for loop in FortuneWheel 's items in SpinningWheelScreen .
items: [
for (var i in widget.inputs)
for (var j = 0; j < i.length; j++)
FortuneItem(child: Text(i[j])),
],
Also you have to remove GestureDetector above your expanded widget or add child to it. because it is causing issue. After removing GestureDetector it works fine.
Result:
How am I supposed to pass a value in this big mess called Flutter?
30 years old php global $var wasn't good?
All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
By the way, I tried using Navigator.push but it seems to open a completely new window, the value is there but I'd need it to show in the tab body not in a new window, below is my code:
main.dart
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
title: Text('Flutter App'),
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.qr_code),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List',
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
// SECOND TAB WIDGET (custom)
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(res!),
),
);
}
}
// FIRST TAB WIDGET (qrcode)
class QRViewExample extends StatefulWidget {
const QRViewExample({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _QRViewExampleState();
}
class _QRViewExampleState extends State<QRViewExample> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
if (result != null)
Text(
'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}')
else
const Text('Scan a code'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Text('Flash: ${snapshot.data}');
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),
),
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.cyanAccent,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
setState(() {
result = scanData;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondView(res: result!.code)))
.then((value) => controller.resumeCamera());
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
How am I supposed to pass a value in this big mess called Flutter?
With state management tools like InheritedWidget, InheritedModel, Provider, BloC and many more.
30 years old php global $var wasn't good? All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
Well, you shouldn't do that and it's not meant to be done like that. We can use several methods to propagate data down the widget tree. Let me explain this with InheritedWidget. But sometimes you want to go for Provider which is a wrapper class for InheritedWidget.
First we create a class named QRListModel which extends InheritedModel:
class QRListModel extends InheritedWidget {
final List<Barcode> qrList = []; // <- This holds our data
QRListModel({required super.child});
#override
bool updateShouldNotify(QRListModel oldWidget) {
return !listEquals(oldWidget.qrList, qrList);
}
static QRListModel of(BuildContext context) {
final QRListModel? result = context.dependOnInheritedWidgetOfExactType<QRListModel>();
assert(result != null, 'No QRListModel found in context');
return result!;
}
}
updateShouldNotify is a method we have to override to tell Flutter, when we want the widgets to rebuild. We want this to happen when the list changes. The of method is just a handy way to access the QRListModel.
Now wrap a parent widget of both the scan tab view and the list tab view inside QRListModel. We go for HomeView:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: QRListModel(child: HomeView()), // <- here!
);
}
}
We can take any parent widget but it should be a class where we don't call setState. Otherwise our QRListModel also gets rebuilt and our list is gone.
Now we can access QRListModel from anywhere inside the subtree. We need it here:
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
this.controller!.resumeCamera();
});
controller.scannedDataStream.listen((scanData) async {
controller.pauseCamera();
QRListModel.of(context).qrList.add(scanData); // <- Here we access the list
await showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Barcode was added!"),
children: [
Text(scanData.code!)
],
)
);
});
}
And here we read the list:
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: QRListModel.of(context).qrList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(QRListModel.of(context).qrList[index].code ?? "NO"),
),
);
}
);
}
}
Now both pages have access to the qr list. Please do mind that a InheritedWidget can only have final fields. So if you need mutable fields, you need an additional wrapper class. We don't need it as we don't change the list but only its elements.
By the way: You shouldn't call setState inside initState. You did this here:
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {}); // <- Don't call setState inside initState!
super.initState();
}
I have 2 pages, in the first page I have a button which is on click will open second page, in second page I have variable number = 999; so when I back to the first page I want to show the number print(number); or display on Text(number) How to do it with dispose() ?
#override
void dispose() {
super.dispose();
// send data to the first page
}
thanks for your answer
You can simply do this with the help of a navigator.
Navigator.push returns a Future that completes after calling
Navigator.pop on the Second Screen with the value passed.
e.x code:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
String _resultNumber = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatefulWidget {
#override
_SelectionButtonState createState() => _SelectionButtonState();
}
class _SelectionButtonState extends State<SelectionButton> {
String _resultNumber;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () => _navigateAndDisplaySelection(context),
child: Text('Pick an option, any number!'),
),
Text(_resultNumber ?? ''),
]);
}
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
_resultNumber = result;
}
}
class SelectionScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick a number'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, '999');
},
child: Text('999 Number'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result.
Navigator.pop(context, '500');
},
child: Text('550 Number'),
),
)
],
),
),
);
}
}
With dispose() you need override the back pressed, to do this wrap the Scaffold in WillPopScope widget.
return WillPopScope(
onWillPop: () {
_backPressed();
return Future.value(false);
},
child: Scaffold(
appBar: AppBar(
title: Text('your text'),
),
body: Center(),
),
);
void _backPressed() {
Navigator.pop(context, '999');
}
I'm creating a registration form in Flutter, and I would like the user to go through steps. Every step should transition to the next step with a sliding effect. For example, if I am on Step 1, moving to Step 2 should slide the form to the left, and I should get Form 2. Then if I go back to form 1, it should slide the form to the right.
Here's an illustration:
I tried to do that with multiple routes:
routes: {
'/': (context) => HomePage(),
'/step1': (context) => FormStep1(),
'/step2': (context) => FormStep2(),
},
Then on submit:
Navigator.push(
context,
EnterExitRoute(exitPage: FormStep1(), enterPage: FormStep2())
);
EnterExitRoute
But that makes the App Bar slide as well, and I want only the form to slide.
With an advice from a friend, I ended up using PageView. That way I didn't have to make a new route for every step.
class _RegisterFormState extends State<RegisterForm> {
final _formsPageViewController = PageController();
List _forms;
#override
Widget build(BuildContext context) {
_forms = [
WillPopScope(
onWillPop: () => Future.sync(this.onWillPop),
child: Step1Container(),
),
WillPopScope(
onWillPop: () => Future.sync(this.onWillPop),
child: Step2Container(),
),
];
return Expanded(
child: PageView.builder(
controller: _formsPageViewController,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return _forms[index];
},
),
);
}
void _nextFormStep() {
_formsPageViewController.nextPage(
duration: Duration(milliseconds: 300),
curve: Curves.ease,
);
}
bool onWillPop() {
if (_formsPageViewController.page.round() ==
_formsPageViewController.initialPage) return true;
_formsPageViewController.previousPage(
duration: Duration(milliseconds: 300),
curve: Curves.ease,
);
return false;
}
}
Explanation:
I'm wrapping every form with WillPopScope so "back" button will
affect navigation.
I'm using physics: NeverScrollableScrollPhysics() option on the PageView builder so it will not be affected by a swipe gesture.
On each button of a form step (except last step) I call the _nextFormStep()
method, which moves to the next form.
The child of each WillPopScope() in the list is simply the form / widget you want to be slided.
as an option you can wrap pages with Navigator widget
something like this
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Title')),
body: SafeArea(
child: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
break;
case '/step1':
return CupertinoPageRoute(builder: (context) => FormStep1());
break;
case '/step2':
return CupertinoPageRoute(builder: (context) => FormStep2());
break;
}
},
),
),
),
),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green[200],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('HomePage'),
RaisedButton(
onPressed: () => Navigator.pushNamed(context, '/step1'),
child: Text('Start'),
),
],
),
);
}
}
class FormStep1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue[200],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('FormStep1'),
RaisedButton(
onPressed: () => Navigator.pushNamed(context, '/step2'),
child: Text('Next'),
),
],
),
);
}
}
class FormStep2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow[200],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('FormStep2'),
RaisedButton(onPressed: () {}, child: Text('Next')),
],
),
);
}
}
also instead of CupertinoPageRoute you can use any custom Route with any transition