I am trying to use Flutter Riverpod together with the Date Range Picker.
I have a list of Trips which when clicked on, open up the Trip Page.
On the trip page, the user can view the start / end dates
They can then click on a button, which brings up the Date Range Picker widget
I want the user to be able to change the date range, and this be reflected on the screen (and eventually saved back against the Trip object)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.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 const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Riverpod DateRange Picker',
home: HomeScreen(),
);
}
}
class Trip {
final String name;
final DateTimeRange dateTimeRange;
Trip(this.name, this.dateTimeRange);
}
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
Set<Trip> trips = {
Trip('Test 1',
DateTimeRange(start: DateTime(2023, 1, 3), end: DateTime(2023, 1, 6)))
};
return Scaffold(
appBar: AppBar(title: const Text('Riverpod DateRange Picker')),
body: ListView(
children: [
for (final trip in trips)
ListTile(
title: Text(trip.name),
onTap: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) =>
TripScreen(trip: trip)));
},
),
],
),
);
}
}
class TripScreen extends ConsumerWidget {
const TripScreen({Key? key, required this.trip}) : super(key: key);
final Trip trip;
#override
Widget build(BuildContext context, WidgetRef ref) {
void _showDateRangePicker() async {
final DateTimeRange? result = await showDateRangePicker(
context: context,
locale: const Locale('en', 'GB'),
initialDateRange: trip.dateTimeRange,
saveText: 'Done',
firstDate: DateTime(2022, 1, 1),
lastDate: DateTime(2030, 12, 31),
);
if (result != null) {
print(result);
}
}
return WillPopScope(
onWillPop: () {
Navigator.pop(context);
return Future.value(false);
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(trip.name),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IconButton(
icon: const Icon(Icons.calendar_month),
onPressed: () {
_showDateRangePicker();
},
),
Text(
'Start: ${DateFormat('dd/MM/yyyy').format(trip.dateTimeRange.start)}'),
Text(
'End: ${DateFormat('dd/MM/yyyy').format(trip.dateTimeRange.end)}'),
],
))));
}
}
Any help would be much appriciated.
Thanks.
I've created this using Provider but I would like to use Riverpod.
I created "tripsProvider" instance of StateProvider that holds all trip data, like in your case only one value. Home screen watching that provider and displaying data in ListView, when user select item in list I pass index of item and watch that single item on new screen (copy of yours TripScreen).
When user update the data of single item I update that list, so update is available on TripScreen and HomeScreen. I also still learning riverpod but hope my answer can help you.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
final tripsProvider = StateProvider<List<Trip>>(
(ref) => [
Trip(
'Test 1',
DateTimeRange(start: DateTime(2023, 1, 3), end: DateTime(2023, 1, 6)),
),
],
);
class Trip {
final String name;
final DateTimeRange dateTimeRange;
const Trip(this.name, this.dateTimeRange);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Riverpod DateRange Picker',
home: HomeScreen(),
);
}
}
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final trips = ref.watch(tripsProvider);
return Scaffold(
appBar: AppBar(title: const Text('Riverpod DateRange Picker')),
body: ListView.builder(
itemCount: trips.length,
itemBuilder: (context, index) {
Trip trip = trips[index];
return ListTile(
title: Text(trip.name),
onTap: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => TripScreen(index: index),
),
);
},
);
},
),
);
}
}
class TripScreen extends ConsumerWidget {
int index;
TripScreen({Key? key, required this.index}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final trip = ref.watch(tripsProvider)[index];
void _showDateRangePicker() async {
final DateTimeRange? result = await showDateRangePicker(
context: context,
locale: const Locale('en', 'GB'),
initialDateRange: trip.dateTimeRange,
saveText: 'Done',
firstDate: DateTime(2022, 1, 1),
lastDate: DateTime(2030, 12, 31),
);
if (result != null) {
print(result);
Trip updatedTrip = Trip(trip.name, DateTimeRange(start: result.start, end: result.end));
ref.read(tripsProvider.notifier).update(
(state) {
List<Trip> updatedList = [];
for (int i = 0; i < state.length; i++) {
if (i == index) {
updatedList.add(updatedTrip);
} else {
updatedList.add(state[i]);
}
}
return updatedList;
},
);
}
}
return WillPopScope(
onWillPop: () {
Navigator.pop(context);
return Future.value(false);
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(trip.name),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IconButton(
icon: const Icon(Icons.calendar_month),
onPressed: () {
_showDateRangePicker();
},
),
Text('Start: ${DateFormat('dd/MM/yyyy').format(trip.dateTimeRange.start)}'),
Text('End: ${DateFormat('dd/MM/yyyy').format(trip.dateTimeRange.end)}'),
],
),
),
),
);
}
}
Related
I have an app that presents an AuthScreen and then works with subsequent screens based on the Auth provider class result.
In fact, in the main.dart file I present different pages based on the provider class Auth.
If the user performs the login (and he is Supplier), he will be directed to SupplierOverviewScreen, where he can see all his events.
If he clicks on an event, he will be directed to the EditEventScreen class, where he can modify the event.
Both SupplierOverviewScreen and EditEventScreen use the same drawer (SupplierDrawer), which allows to perform the logout operation.
When peforming a logout, Auth info will be deleted and so the main.dart file (consuming Auth provider class) will present againt the AuthScreen page.
If I'm on the SupplierOverviewScreen and I open the drawer to logout, everything works.
The problem is that if I'm on the EditEventScreen and I try to logout, The screen remains stuck and the drawer doesn't pop out.
I see that under the hood everything works, and the main.dart file returns the AuthScreen exactly as it does in SupplierOverviewScreen (where it works), but nothing changes on the screen.
If I return to previous screen, it doesn't direct me to SupplierOverview screen, but to AuthScreen.
I've found two possible hacks, but I'm not satisfied with them:
Remove drawer from EditEventScreen
Change the drawer such that after the logout it performs Navigator.pushReplacementNamed(context, "/"); It works, but since main.dart file consumes Auth provider class, I have that the home is called twice (one when Auth provider class notify listeners and one when drawer calls Navigator.pushReplacementNamed(context, "/")
Do you have any idea to suggest? Thanks a lot
This is the main.dart file:
Future<void> main() async {
await dotenv.load(fileName: Environment.fileName);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
//Init state operations
}
void _configureAmplify() async {
// Amplify initial configurations
}
List<Event> _mockEvents() {
// mocked list of event objects
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Auth()),
ChangeNotifierProxyProvider<Auth, Supplier>(
create: (ctx) => Supplier(userId: null, username: null),
update: (ctx, authData, previous) => Supplier(
userId: authData.getUserId, username: previous?.username),
),
ChangeNotifierProxyProvider<Auth, SupplierEvents>(
create: (ctx) => SupplierEvents(
userId: null,
events:
_mockEvents()),
update: (ctx, authData, previous) => SupplierEvents(
userId: authData.getUserId, events: previous?.events ?? []),
),
],
child: Consumer<Auth>(
builder: (context, authData, child) => MaterialApp(
title: 'Apperò',
theme: ThemeData(
colorScheme: Theme.of(context).colorScheme.copyWith(),
),
home: !authData.isAuth()
? FutureBuilder(
future: authData.tryAutoLogin(),
builder: (ctx, authResultSnapshot) {
print(authResultSnapshot.connectionState.name);
if (authResultSnapshot.connectionState ==
ConnectionState.waiting) {
return LoadingScreen();
} else
return AuthScreen();
})
: (authData.getUserType == UserType.supplier
? SupplierOverviewScreen()
: CustomerOverviewScreen()),
routes: {
SupplierOverviewScreen.ROUTE_NAME: (ctx) =>
SupplierOverviewScreen(),
CustomerOverviewScreen.ROUTE_NAME: (ctx) =>
CustomerOverviewScreen(),
EditEventScreen.ROUTE_NAME: (ctx) => EditEventScreen(),
}),
),
);
}
}
This is the SupplierOverviewScreen class:
class SupplierOverviewScreen extends StatefulWidget {
static const String ROUTE_NAME = '/supplier-overview-screen';
const SupplierOverviewScreen({Key? key}) : super(key: key);
#override
State<SupplierOverviewScreen> createState() => _SupplierOverviewScreenState();
}
class _SupplierOverviewScreenState extends State<SupplierOverviewScreen> {
late Future _obtainedInfo;
Future<void> _fetchInfo() async {
await Provider.of<Supplier>(context, listen: false).fetchSupplierByUserId();
await Provider.of<SupplierEvents>(context, listen: false)
.fetchEventsByUserId();
}
#override
void initState() {
_obtainedInfo = _fetchInfo();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Your page'), actions: []),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: RefreshIndicator(
onRefresh: () => _fetchInfo(),
child: Column(
children: [
Consumer<Supplier>(
builder: (context, supplierData, _) => Padding(
padding: EdgeInsets.all(8),
child: Text('Hello ${supplierData.username}'),
),
),
const SizedBox(
height: 10,
),
Consumer<SupplierEvents>(
builder: (context, supplierEventsData, _) => Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: supplierEventsData.events.length,
itemBuilder: (context, index) => Column(children: [
SupplierEventsItem(
event: supplierEventsData.events[index],
),
const Divider(),
]),
),
),
),
const SizedBox(
height: 10,
),
Padding(
padding: EdgeInsets.all(8),
child: Text('Bottom element'),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Text('Add Event', textAlign: TextAlign.center,),
onPressed: () {
Navigator.of(context)
.pushNamed(EditEventScreen.ROUTE_NAME);
},
));
}
}
This is the EditEventScreen class:
class EditEventScreen extends StatefulWidget {
static const String ROUTE_NAME = '/edit-event-screen';
const EditEventScreen({Key? key}) : super(key: key);
#override
State<EditEventScreen> createState() => _EditEventScreenState();
}
class _EditEventScreenState extends State<EditEventScreen> {
late Future _obtainedInfo;
Event? inputEvent;
var _isLoading = false;
var _form = GlobalKey<FormState>();
var _titleFocusNode = FocusNode();
var _descriptionFocusNode = FocusNode();
Map<String, dynamic?> _initialValuesMap = {
//....
};
Event event = Event(// ...);
#override
void didChangeDependencies() {
_obtainedInfo = _fetchInfo();
super.didChangeDependencies();
}
#override
void dispose() {
// ...
}
Future<void> _fetchInfo() async {
try {
var eventId = ModalRoute.of(context)!.settings.arguments as int?;
print('eventId: $eventId');
if (eventId != null) {
inputEvent = await Provider.of<SupplierEvents>(context, listen: false)
.findById(eventId);
_initFormFields();
}
} catch (error) {
print(error);
}
}
void _initFormFields() {
// init form fields logic
}
Future<void> _saveForm() async {
//Form saving logic
}
#override
Widget build(BuildContext context) {
print('Building EditEventScreen');
return Scaffold(
appBar: AppBar(title: Text('Manage event'), actions: [
IconButton(
onPressed: () {
_saveForm();
},
icon: Icon(Icons.check))
]),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _form,
child: SingleChildScrollView(
child: Column(
children: [
// TextFormFields ...
],
),
),
),
),
),
);
}
}
This is the drawer class:
class SupplierDrawer extends StatelessWidget {
Future<void> logout(BuildContext context) async {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Logout'),
content: Container(
alignment: Alignment.center,
height: 300,
width: 300,
child: Column(
children: const [
Text('Logging out...'),
Center(
child: CircularProgressIndicator(),
)
],
),
),
);
},
);
await Provider.of<Auth>(context, listen: false).signOutCurrentUser(false);
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
AppBar(
title: Text('Your options'),
automaticallyImplyLeading: false,
),
Divider(),
ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
onTap: () async {
await logout(context);
},
),
],
),
);
}
}
I'm trying to make a note app but there is a yellow square showing on the screen.
I've included the main.dart code and also allnotesscreens.dart. I think there is something wrong with allnotesscreens code, but I don't know what.
Maybe _loadViewMode() part.
Why this problem is happening?!!!
Main.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'providers/label_provider.dart';
import 'providers/note_provider.dart';
import 'package:provider/provider.dart';
import 'constants/app_constants.dart';
import 'screens/all_labels_screen.dart';
import 'screens/all_notes_screen.dart';
import 'screens/drawer_screen.dart';
main() {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: ColorsConstant.grayColor,
),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => NoteProvider()),
ChangeNotifierProvider(create: (_) => LabelProvider()),
],
builder: (context, child) => MaterialApp(
title: 'Note-App',
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.dark,
theme: customThemeData(context),
initialRoute: '/',
routes: {
'/': (context) => const AllNotesScreen(),
DrawerScreen.routeName: (context) => const DrawerScreen(),
AllLabelsScreen.routeName: (context) => const AllLabelsScreen(),
},
),
);
}
}
allnotesscreens.dart:
class AllNotesScreen extends StatefulWidget {
const AllNotesScreen({Key? key}) : super(key: key);
#override
State<AllNotesScreen> createState() => _AllNotesScreenState();
}
class _AllNotesScreenState extends State<AllNotesScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Container(
height: 200,
width: 100,
color: Colors.yellow,
),
),
);
}
String _viewMode = ViewMode.staggeredGrid.name;
bool _isLoading = false;
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
setState(() {
_isLoading = true;
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future _loadViewMode() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('view-mode')) return;
setState(() {
_viewMode = prefs.getString('view-mode') ?? ViewMode.staggeredGrid.name;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text(
"all notes",
style: TextStyleConstants.titleAppBarStyle,
),
actions: [
if (context
.watch<NoteProvider>()
.items
.isNotEmpty)
IconButton(
onPressed: () {
showSearch(
context: context,
delegate: NoteSearch(isNoteByLabel: false),
);
},
icon: const Icon(Icons.search),
),
IconButton(
onPressed: () async {
final result = await changeViewMode(_viewMode);
setState(() {
_viewMode = result;
});
},
icon: _viewMode == ViewMode.staggeredGrid.name
? const Icon(Icons.view_stream)
: const Icon(Icons.grid_view),
),
const SizedBox(
width: 6,
)
],
),
drawer: const DrawerScreen(),
body: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: RefreshIndicator(
onRefresh: () => refreshOrGetData(context),
child: Consumer<NoteProvider>(
builder: (context, noteProvider, child) =>
noteProvider.items.isNotEmpty
? NoteListViewWidget(
notes: noteProvider.items,
viewMode: _viewMode,
scaffoldContext: _scaffoldKey.currentContext!,
)
: child!,
child: const NoNoteUIWidget(
title: "your notes after adding will appear here",
),
),
),
floatingActionButton: FloatingActionButton(
child: linearGradientIconAdd,
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const EditNoteScreen(),
));
},
),
);
}
}
}
The first few lines of your _AllNotesScreenState class are why there's a yellow square; that's what you're telling it to build.
class _AllNotesScreenState extends State<AllNotesScreen> {
// this build function here is what is drawing to the screen
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Container(
height: 200,
width: 100,
color: Colors.yellow,
),
),
);
}
Maybe it's just how you've pasted it in, but it appears as though you have a build function defined within the didChangeDependencies function. If you took it out of there, it would then make it apparent that you have two build functions defined for the class.
I'm assuming it's the second one that you actually want building.
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future _loadViewMode() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('view-mode')) return;
setState(() {
_viewMode = prefs.getString('view-mode') ?? ViewMode.staggeredGrid.name;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
...
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 am getting an Error: Could not find the correct Provider above this ProductItemScreen Widget whenever I press on a product Item even though I have set my provider as a parent of MaterialApp()
Below is the product_overview_screen.dart file which shows a gridview of the products on my screen
class ProductsOverviewScreen extends StatelessWidget {
static const routeName = '/prod_overview_Screen';
const ProductsOverviewScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodData = Provider.of<Products>(context); //Recieves data from provider file
return Scaffold(
appBar: AppBar(
title: const Text('My Shop'),
backgroundColor: Theme.of(context).primaryColor,
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3 / 2,
),
itemCount: prodData.items.length,
itemBuilder: (ctx, i) => ProductItem(
imageUrl: prodData.items[i].imageUrl,
title: prodData.items[i].title,
id: prodData.items[i].id,
),
),
);
}
}
Below is also the Product_item.dart file where I pass the Id of the product to the next screen where I display the data of that particular product on the screen
class ProductItemScreen extends StatelessWidget {
static const routeName = '/prod_item_screen';
const ProductItemScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodIdPassed = ModalRoute.of(context)!.settings.arguments as String;
final prod = Provider.of<Products>(context).findById(prodIdPassed);
return Scaffold(
appBar: AppBar(
title: Text(prod.title),
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
children: [Image.asset(prod.imageUrl), Text(prod.title)],
),
);
}
}
Also below is the main.dart file where I have setup my provider for all available listeners
void main() {
runApp(
ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
),
);
}
Right below runApp, explicitly declare the class:
ChangeNotifierProvider<Products>(
You did'nt provided providermodel class here Check this provider.
Mainmethod (not changed)
void main() {
var changeNotifierProvider = ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
);
runApp(changeNotifierProvider);
}
Products provider (Added)
class Products extends ChangeNotifier {
Product? _findById;
List<Product> _items = [];
List<Product> get items => _items;
set items(List<Product> value) {
_items = value;
notifyListeners();
}
set items2(List<Product> value) {
_items = value;
// notifyListeners();
}
Product? get findById => _findById;
set findById(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
notifyListeners();
}
set findById2(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
// notifyListeners();
}
}
Sample Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
var changeNotifierProvider = ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
theme: ThemeData.light().copyWith(primaryColor: Colors.purple),
initialRoute: ProductsOverviewScreen.routeName,
routes: {
ProductsOverviewScreen.routeName: (ctx) =>
const ProductsOverviewScreen(),
ProductItemScreen.routeName: (ctx) => const ProductItemScreen(),
},
),
);
runApp(changeNotifierProvider);
}
class ProductItemScreen extends StatelessWidget {
static const routeName = '/prod_item_screen';
const ProductItemScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodIdPassed = ModalRoute.of(context)!.settings.arguments as String;
var product = new Product(imageUrl: "", title: "", id: prodIdPassed);
Provider.of<Products>(context).findById2 = product;
final prod = Provider.of<Products>(context).findById;
return Scaffold(
appBar: AppBar(
title: Text(prod!.title),
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
children: [Image.network(prod.imageUrl), Text(prod.title)],
),
);
}
}
class ProductsOverviewScreen extends StatelessWidget {
static const routeName = '/prod_overview_Screen';
const ProductsOverviewScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final prodData = Provider.of<Products>(context);
List<Product> item = [];
item.add(Product(
imageUrl:
"https://cdn.pixabay.com/photo/2013/07/13/14/08/apparel-162192_1280.png",
title: "lava",
id: "1"));
item.add(Product(
imageUrl:
"https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/classic-accessories-1516305397.jpg",
title: "hi",
id: "2"));
item.add(Product(
imageUrl: "https://images.indianexpress.com/2019/09/toys.jpg",
title: "hi",
id: "4"));
item.add(Product(
imageUrl: "https://m.media-amazon.com/images/I/51zEsraniRL._UX569_.jpg",
title: "hi",
id: "3"));
prodData.items2 = item; //Recieves data from provider file
return Scaffold(
appBar: AppBar(
title: const Text('My Shop'),
backgroundColor: Theme.of(context).primaryColor,
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3 / 2,
),
itemCount: prodData.items.length,
itemBuilder: (ctx, i) => ProductItem(
imageUrl: prodData.items[i].imageUrl,
title: prodData.items[i].title,
id: prodData.items[i].id,
),
),
);
}
}
class ProductItem extends StatelessWidget {
var imageUrl;
var id;
var title;
ProductItem({Key? key, this.imageUrl, this.title, this.id}) : super(key: key);
#override
Widget build(BuildContext context) {
var column = Column(
children: [
Expanded(
child: Container(
// height: 125,
child: Image.network(
imageUrl,
alignment: Alignment.center,
fit: BoxFit.cover,
),
),
),
Center(
child: Text(
title + " " + id,
style: TextStyle(fontSize: 12),
))
],
);
return InkWell(
onTap: () {
Navigator.pushNamed(context, ProductItemScreen.routeName,
arguments: id);
},
child: column);
}
}
class Product {
var title;
var imageUrl;
var id;
Product({this.imageUrl, this.title, this.id});
}
class Products extends ChangeNotifier {
Product? _findById;
List<Product> _items = [];
List<Product> get items => _items;
set items(List<Product> value) {
_items = value;
notifyListeners();
}
set items2(List<Product> value) {
_items = value;
// notifyListeners();
}
Product? get findById => _findById;
set findById(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
notifyListeners();
}
set findById2(Product? value) {
_findById = _items.where((element) => element.id == value!.id).first;
// notifyListeners();
}
}
I'm new in Flutter. I'm trying to push a List from NewData to FillData screen with pushNamed. But it said:
The following _TypeError was thrown while handling a gesture:
type 'FillData' is not a subtype of type 'List'
If i remove the comment in '/FillData', i receive null data instead. What should i do?
This is my code:
SettingNavigator
class SettingNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => Home(),
'/NewData': (context) => NewData(),
// '/FillData': (context) => FillData(), (in comment)
}
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
final ChartGroupData chartName = setting.arguments;
final List<ChartGroupData> groupNames = setting.arguments;
return MaterialPageRoute(builder: (context) {
return FillData(
chartName: chartName,
gName: groupNames,
);
});
}
return null;
},
);
}
}
NewData
import 'package:flutter/material.dart';
class NewData extends StatefulWidget {
List<ChartGroupData> groupNames;
NewData({Key key, #required this.groupNames}) : super(key: key);
#override
NewDataStage createState() => NewDataStage();
}
class NewDataStage extends State<NewData> {
TextEditingController _nameCtrl = new TextEditingController();
var textFields = <Widget>[];
var groupTECs = <TextEditingController>[];
#override
void initState() {
super.initState();
textFields.add(createCustomTextField());
}
Widget createCustomTextField() {
var groupCtrl = TextEditingController();
groupTECs.add(groupCtrl);
return Container(
padding: EdgeInsets.fromLTRB(0, 5, 0, 0),
child: Row(
children: <Widget>[
Expanded(flex: 3, child: Text("Group ${textFields.length}")),
Container(
constraints: BoxConstraints.tightFor(width: 120, height: 60),
child: TextField(
controller: groupCtrl,
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(child: Text("New Chart")),
),
body: Container(
alignment: AlignmentDirectional.center,
constraints: BoxConstraints.expand(),
child: Column(
children: <Widget>[
Text(
"Your chart name",
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
TextField(
style: TextStyle(fontSize: 20),
controller: _nameCtrl,
),
Expanded(
flex: 3,
child: Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: textFields.length,
itemBuilder: (BuildContext context, int index) {
return textFields[index];
},
),
),
),
SizedBox(
height: 60,
width: 120,
child: RaisedButton(
onPressed: _onTapNext,
child: Text("NEXT"),
color: Colors.green,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _onTapCreate,
child: Icon(Icons.add, color: Colors.white),
shape: CircleBorder(),
),
),
);
}
void _onTapNext() {
/// Push Groups name to FillData
widget.groupNames = List<ChartGroupData>();
for (int i = 0; i < textFields.length; i++) {
var name = groupTECs[i].text;
widget.groupNames.add(ChartGroupData(name));
}
print(widget.groupNames.toString());
Navigator.pushNamed(context, '/FillData',
arguments: FillData(
gName: widget.groupNames,
chartName: ChartGroupData(_nameCtrl.text),
));
}
void _onTapCreate() {
setState(() {
textFields.add(createCustomTextField());
});
}
}
FillData
class FillData extends StatefulWidget {
final ChartGroupData chartName;
final List<ChartGroupData> gName;
FillData({Key key, #required this.chartName, #required this.gName})
: super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
class FillDataStage extends State<FillData> {
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.gName);
print(widget.chartName);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(
child: Text("Fill your Data"),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
_onTapPrintReceivedData();
_showDialog();
},
child: Text("Print Data"),
),
),
));
}
}
Class ChartGroupData
lass ChartGroupData {
final String groupNames;
ChartGroupData(this.groupNames);
#override
String toString() {
return 'Group: $groupNames';
}
}
You have 2 problems with your code:
1- you cant user routes with onGenerateRoute, because now the app doesn't know where to go, to the widget that you didn't pass anything to (inside routes) or to the widget inside the onGenerateRoute.
2- arguments is a general object that you can put whatever you want inside of it, and doing this:
final ChartGroupData chartName = setting.arguments; final
List groupNames = setting.arguments;
passes the same value to two different objects, I solved this by doing the following (it's not the best but will give you a rough idea of what you should do)
created a new object that contains the data to be passed:
class ObjectToPass {
final ChartGroupData chartName;
final List<ChartGroupData> groupNames;
ObjectToPass({this.chartName, this.groupNames});
}
changed FillData implementation:
class FillData extends StatefulWidget {
final ObjectToPass objectToPass;
FillData({Key key, #required this.objectToPass}) : super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
...
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.objectToPass.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.objectToPass.groupNames);
print(widget.objectToPass.chartName);
}
to navigate to FillData you would:
Navigator.pushNamed(
context,
'/FillData',
arguments: ObjectToPass(
chartName: ChartGroupData(_nameCtrl.text),
groupNames: groupNames,
),
);
finally this is how your MaterialApp should look like:
return MaterialApp(
initialRoute: '/NewData',
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
return MaterialPageRoute(builder: (context) {
return FillData(
objectToPass: setting.arguments,
);
});
} else if (setting.name == '/NewData') {
return MaterialPageRoute(builder: (_) => NewData());
}
return null;
},
);
you can pass a list instead of the object I created and get your objects from it by it's index.