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);
},
),
],
),
);
}
}
Related
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();
}
How can I send a string from another route to a route that has the same block?
my problem is dataLoading because when I send data from another route I still have DataLoadingState so as I speak I need to send a string to another route which has the same block
I have already tried everything, please help
firstPage
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Barcode Scanner')),
body: BlocProvider<ScannerBloc>(
create: (context) => ScannerBloc(), child: MainPageView()),
);
}
}
class MainPageView extends StatefulWidget {
MainPageView({Key? key}) : super(key: key);
#override
State<MainPageView> createState() => _MainPageViewState();
}
class _MainPageViewState extends State<MainPageView> {
#override
Widget build(BuildContext context) {
return Column(
children: [
BlocBuilder<ScannerBloc, ScannerState>(
builder: (context, state) {
if (state is DataLoading) {
return Center(
child: Text("Loading Data..."),
);
}
if (state is DataLoaded) {
if (state.barCode.isEmpty) {
return Center(
child: Text(' EMPTY'),
);
}
return Center(
child: Text(state.barCode),
);
// ListView.builder(
// shrinkWrap: true,
// itemCount: state.barCode.length,
// itemBuilder: (context, index) {
// return state.barCode;
// },
// );
}
if (state is DataError) {
return Center(
child: Text(state.error),
);
}
return throw Exception('something bad happen ');
},
),
BlocBuilder<ScannerBloc, ScannerState>(builder: (context, state) {
return ElevatedButton(
child: const Text('Add BarCode'),
onPressed: () {
// context.read<ScannerBloc>().add(GetBarCode(barCode: 'TEST')); <<-- THAT WORKING BUT I WANT THE SAME RESULT IN ANOITHER ROUTE
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ScannerPagee(),
),
);
});
})
],
);
}
}
secondPage
BlocBuilder<ScannerBloc, ScannerState>(builder: (context, state) {
return ElevatedButton(
child: const Text('send BarCode'),
onPressed: () {
BlocProvider.of<ScannerBloc>(context).add(
GetBarCode(barCode: 'ExampleoFBarCode'),
);
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MainPage(),
),
);
});
})
i have a screen with three widget [widgetA, widgetB, widgetC]
and i have a bloc[BlocA] which is responsible for the data fetching and displaying on this screen
i have three event [eventA, eventB, eventC] which render the widget [widgetA, widgetB, widgetC]
and i have three state [stateA, stateB, stateC] which are responsible for managing state of widget [widgetA, widgetB, widgetC]
i have attached all code to reproduce and test the case.
I am only able to display one state and their respective widget at a time whereas i want to display all three state and its widget based on their event.
any help would be highly appreciated.
only way i tried to achieve the same is by making separate bloc and event class for each widget, but somehow i am not satisfied with this approach.
what would be the best approach to achieve this use case.
TestScreen
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:locus/blocs/test/testbloc.dart';
class TestScreen extends StatelessWidget {
const TestScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TestBloc()..add(const TestEvent1()),
child: Scaffold(
appBar: AppBar(
title: const Text('Test'),
),
body: Stack(
children: [
Builder(builder: (context) {
return Padding(
padding: const EdgeInsets.only(top: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () =>
context.read<TestBloc>().add(const TestEvent1()),
child: const Text("Event1")),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => context
.read<TestBloc>()
.add(const TestEvent2(" event 2")),
child: const Text("Event2")),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => context
.read<TestBloc>()
.add(const TestEvent3(false)),
child: const Text("Event3")),
],
),
);
}),
BlocBuilder<TestBloc, TestState>(
builder: (context, state) {
if (state is TestState1) {
return const Center(child: Text("I am state 1"));
}
return const SizedBox.shrink();
},
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<TestBloc, TestState>(
builder: (context, state) {
if (state is TestState2) {
return Center(
child: Text("I am state 2 ${state.message}"));
}
return const SizedBox.shrink();
},
),
BlocBuilder<TestBloc, TestState>(
builder: (context, state) {
if (state is TestState3) {
return Center(
child: Text("I am state 3 ${state.check}"));
}
return const SizedBox.shrink();
},
),
],
),
],
),
));
}
}
TestBloc
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc/bloc.dart';
part 'test_state.dart';
part 'test_event.dart';
class TestBloc extends Bloc<TestEvent, TestState> {
TestBloc() : super(TestInitializing()) {
on<TestEvent1>((event, emit) => test1(event, emit));
on<TestEvent2>((event, emit) => test2(event, emit));
on<TestEvent3>((event, emit) => test3(event, emit));
}
Future test1(TestEvent1 event, Emitter<TestState> emit) async {
try {
emit(const TestState1());
} catch (_) {}
}
Future test2(TestEvent2 event, Emitter<TestState> emit) async {
try {
emit(const TestState2(message: "Hello"));
} catch (_) {}
}
Future test3(TestEvent3 event, Emitter<TestState> emit) async {
try {
emit(const TestState3(check: true));
} catch (_) {}
}
}
TestEvent
#immutable
abstract class TestEvent extends Equatable {
const TestEvent();
}
class TestEvent1 extends TestEvent {
const TestEvent1();
#override
List<Object> get props => [];
}
class TestEvent2 extends TestEvent {
final String message;
const TestEvent2(this.message);
#override
List<Object> get props => [message];
}
class TestEvent3 extends TestEvent {
final bool check;
const TestEvent3(this.check);
#override
List<Object> get props => [check];
}
TestState
#immutable
abstract class TestState extends Equatable {
const TestState();
}
class TestInitializing extends TestState {
#override
List<Object> get props => [];
}
class TestState1 extends TestState {
const TestState1();
#override
List<Object?> get props => [];
}
class TestState2 extends TestState {
final String message;
const TestState2({
required this.message,
});
#override
List<Object> get props => [message];
}
class TestState3 extends TestState {
final bool check;
const TestState3({
required this.check,
});
#override
List<Object> get props => [check];
}
testbloc barrel class
export 'test_bloc.dart';
A bloc can only have one state at a time. If you want more states than that you'll have to either maintain a custom internal state mechanism inside TestBloc or create 3 separate TestBlocs and then provide each BlocBuilder with each TestBloc like so:
class TestScreen extends StatelessWidget {
TestScreen({Key? key}) : super(key: key) {
}
final TestBloc bloc1 = TestBloc();
final TestBloc bloc2 = TestBloc();
final TestBloc bloc3 = TestBloc();
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TestBloc()..add(const TestEvent1()),
child: Scaffold(
appBar: AppBar(
title: const Text('Test'),
),
body: Stack(
children: [
Builder(builder: (context) {
return Padding(
padding: const EdgeInsets.only(top: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () =>
bloc1.add(const TestEvent1()),
child: const Text("Event1")),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => bloc2.add(const TestEvent2(" event 2")),
child: const Text("Event2")),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => bloc3.add(const TestEvent3(false)),
child: const Text("Event3")),
],
),
);
}),
BlocBuilder<TestBloc, TestState>(
bloc: bloc1,
builder: (context, state) {
if (state is TestState1) {
return const Center(child: Text("I am state 1"));
}
return const SizedBox.shrink();
},
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<TestBloc, TestState>(
bloc: bloc2,
builder: (context, state) {
if (state is TestState2) {
return Center(
child: Text("I am state 2 ${state.message}"));
}
return const SizedBox.shrink();
},
),
BlocBuilder<TestBloc, TestState>(
bloc: bloc3,
builder: (context, state) {
if (state is TestState3) {
return Center(
child: Text("I am state 3 ${state.check}"));
}
return const SizedBox.shrink();
},
),
],
),
],
),
));
}
}
However making 3 separate blocs (TestBloc1, TestBloc2, TestBloc3) isn't necessaryly a bad way to go in regards to speration of concerns.
I'm pretty late to the party but I've implemented a package that does exactly what you want !
Here you go : https://pub.dev/packages/multi_state_bloc
You can try this code:
buildWhen: (previous, current) => current is TestState1 && previous != current.
Here I am using provider package for state management.
I have a SpeedDial widget in the floatingActionButton. And whenever I add something in the list mainGoalList by using speedDial and do Navigator.pop(context); it does go back to the page but does not update the list.
SpeedDial
SpeedDialChild(
child: Icon(Icons.book),
backgroundColor: Colors.blue,
label: 'Add Notes',
labelStyle: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context)
.viewInsets
.bottom),
child: AddNotes(),
),
));
}),
AddNote
Center(
child: FlatButton(
onPressed: () {
if (_actcontroller.text == null) {
print("Cannot add null topic");
} else {
addingTheNotes();
Navigator.pop(context);
}
},
child: Text("Add"),
color: Colors.blue,
),
)
Adding the notes
addingTheNotes() {
theDataProvider.ourAllnotes.add(
TodaysNoteClass(
note: _actcontroller.text,
dateTime: theDataProvider.notesChoosenDate,
status: false,
),
);
theDataProvider.showingTheTodaysList();
}
Here is the change notifier
List<TodaysNoteClass> _ourAllnotes = [];
List<TodaysNoteClass> get ourAllnotes => _ourAllnotes;
set ourAllnotes(List<TodaysNoteClass> val) {
_ourAllnotes = val;
notifyListeners();
}
Consumer class, this is where I am showing the notes
class HomePage extends StatefulWidget {
static const String id = 'homePage';
final String todaysDate =
DateFormat('d MMMM').format(DateTime.now()).toLowerCase();
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var theDataProvider;
Widget build(BuildContext context) {
theDataProvider = Provider.of<TheData>(context, listen: false);
return Consumer<TheData>(
builder: (context, value, child) => SingleChildScrollView(
child: Container(
child: Column(
children: [
Container(
height: 120,
child: TodaysNote(),
),
],
),
),
),
);
}
}
And this is the TodaysNote class
class TodaysNote extends StatefulWidget {
TodaysNote({Key key}) : super(key: key);
bool boxChecked = false;
#override
_TodaysNoteState createState() => _TodaysNoteState();
}
class _TodaysNoteState extends State<TodaysNote> {
var theDataProvider;
Widget build(BuildContext context) {
theDataProvider = Provider.of<TheData>(context, listen: false);
return Consumer<TheData>(
builder: (context, value, child) => Container(
child: theDataProvider.showingTheTodaysList(),
),
);
}
}
But now when I go to another screen and then come to the previous screen. I see the updated list.
I have wrapped the main file with provider, wrapped the files with consumer but did not work.
What might be the reason behind this?
try using this:
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage()), ).then((value) => setState(() {}));
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.