Two providers in one Widget - Flutter - android

I have this problem. In my App I'm using the Provider package to manage the login State. In the MaterialApp I also want to manage some sort of the user configuration, in this case the Theme selection.
If I try to use two times Provider.of<LoginService>(context) I'm receiving this error:
Could not find the correct Provider<LoginService> above this MyApp Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice.
How can I use in Provider more of one time the Provider.of... or even two different Providers in a Widget (to, for instance, separate my LoginService and my UserconfigService)?
Thank you!
Actual code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LoginService>(
create: (context) => LoginService(),
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
routes: {
'/': (BuildContext context) {
var state = Provider.of<LoginService>(context);
if (state.isLoggedIn()) {
return HomeScreen();
} else {
return LoginScreen();
}
},
MentorScreen.id: (BuildContext context) => MentorScreen(),
},
)
);
}
My objective:
child: MaterialApp(
title: 'MyApp',
debugShowCheckedModeBanner: false,
theme: state.isDarkThemeEnabled() == true ? ThemeData.dark() : ThemeData.light(),
...

You can use MultiProvider instead of ChangeNotifierProvider.
Read more in here.

This type of Error comes when you use the context just after creating the ChangeNotifierProvider class.
Similarly, if you use the context of the Scaffold to showDialog gives a similar error.
Here is the answer that explains why this happens
For this Wrap, your MaterialApp Widget inside Builder Class which will wait for the class to build first then call the Provider.of<T>(context) method.
Builder(
builder: (context) {
return MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
routes: {
'/': (BuildContext context) {
var state = Provider.of<LoginService>(context);
if (state.isLoggedIn()) {
return HomeScreen();
} else {
return LoginScreen();
}
},
MentorScreen.id: (BuildContext context) => MentorScreen(),
},
);
},
),
and for Two Providers in the same widget.
Use MultiProvider.
here's code from one of my app.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => locator<FAuthService>(),
builder: (context, _) {
return MultiProvider(
child: MaterialApp(
onGenerateRoute: Router.onGenerateRoute,
initialRoute: initialRoute,
navigatorKey: locator<NavigationService>().globalKey,
debugShowCheckedModeBanner: false,
title: 'Demo',
theme: ThemeData(
primaryColor: Colors.black,
),
),
providers: [
ChangeNotifierProvider<HomeVM>(
create: (_) => locator<HomeVM>(),
),
ChangeNotifierProvider<LoginVM>(
create: (context) => locator<LoginVM>(),
),
],
);
});
}
}

Related

Passing captured image between pages in flutter

I'm having problems with my code as I'm getting undefined_identifier errors.
I'm trying to pass the image I captured from 'GeneratedGroup1Widget1.dart' using 'flutter_screenutils' to 'GeneratedResultsWidget.dart'. However, my route in 'main.dart' doesn't define the image variable inside my parameter. I've been trying to fix this error for 10 hours now. Please help. Thanks in advance!
Error: (main.dart)
Undefined name 'image'.
Try correcting the name to one that is defined, or defining the name.
Here are my codes:
'GeneratedGroup1Widget1.dart'
class GeneratedGroup1Widget1 extends StatefulWidget {
#override
_GeneratedGroup1Widget1State createState() => _GeneratedGroup1Widget1State();
}
class _GeneratedGroup1Widget1State extends State<GeneratedGroup1Widget1> {
Future _pickImage() async {
final imageSource = await showDialog<ImageSource>(
context: context,
builder: (context) => SimpleDialog(
title: const Text('Select Image Source'),
children: [
SimpleDialogOption(
onPressed: () => Navigator.pop(context, ImageSource.camera),
child: const Text('Camera'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(context, ImageSource.gallery),
child: const Text('Gallery'),
),
],
),
);
if (imageSource != null) {
final image = await ImagePicker().pickImage(source: imageSource);
Navigator.pushNamed(context, '/GeneratedResultsWidget', arguments: image);
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _pickImage(),
child: Container(
....
'GeneratedResultsWidget.dart'
class GeneratedResultsWidget extends StatelessWidget {
final XFile file;
const GeneratedResultsWidget({required Key key, required this.file})
: super(key: key);
#override
Widget build(BuildContext context) {
if (file == null) {
return Scaffold(body: Center(child: Text('No Image selected')));
} else {
return Scaffold(body: Center(child: Text(file.path)));
}
}
}
class GeneratedResultsWidget1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Material(
...
'main.dart'
void main() {
runApp(FoodClassifierApp());
}
class FoodClassifierApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(360, 640),
builder: (BuildContext context, child) => MaterialApp(
title: 'food-classifier',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/GeneratedHomepageWidget',
routes: {
'/GeneratedScanWidget': (context) => GeneratedScanWidget(),
'/GeneratedResultsWidget': (context) =>
GeneratedResultsWidget(key: UniqueKey(), file: image),
'/GeneratedHomepageWidget': (context) => GeneratedHomepageWidget(),
'/GeneratedFoodlistWidget': (context) => GeneratedFoodlistWidget(),
},
),
);
}
}
This is my first time coding in flutter and I used figma to generate my widgets.

How to change the state of a Switch in a Stateful Widget, retrieving data from a Provider?

I have a Switch on a screen and I need it to use the value that is in a Provider. I've tried to infer this value using the provider's value, but the Switch is immobile, it doesn't change visually(but the value is changed in the DB), it only works as it should when I remove the provider's inferences.
My Provider: (It is being called when I start the application)
class DailyDatabase with ChangeNotifier {
bool notificationActive = false;
void loadDailyData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
notificationActive = prefs.getBool('notificationActive') ?? false;}
Variable:
#override
Widget build(BuildContext context) {
final provider = Provider.of<DailyDatabase>(context);
_notificationActive = provider.notificationActive;
Switch:
Switch(
value: _notificationActive,
onChanged: (value) {
_notificationActive = value;
provider.setNotification(value);
},
),
Stateful Version - Provider only
Here's a very basic example of Provider with a Switch and using StatefulWidget and its setState to refresh the widget (instead of using ChangeNotifierProvider and Consumer to "listen" and "localize" the widget rebuild to just the Switch and the Text label, which is perhaps a more typical use):
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SwitchProviderPage extends StatefulWidget {
#override
_SwitchProviderPageState createState() => _SwitchProviderPageState();
}
class Database {
bool active = false;
void setActive(bool value) {
active = value;
}
}
class _SwitchProviderPageState extends State<SwitchProviderPage> {
#override
Widget build(BuildContext context) {
return Provider(
create: (context) => Database(),
child: Builder(
builder: (context) {
Database db = Provider.of<Database>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: Text('Switch Field'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Active? ${db.active}'),
Switch(
onChanged: (val) { // ← remember to use val (bool)
print('Switch value: $val');
setState(() {
db.setActive(val);
// this sets the Switch setting on/off
});
},
value: db.active,
)
],
),
),
),
);
},
),
);
}
}
Note:
The use of Builder in above is only to make Scaffold be a child of Provider.
Otherwise, Scaffold would be a sibling, not a child, and Provider will not work. Since you wrap your entire app in your ChangeNotifierProvider, you don't need to do this. I needed to do this to get a self-contained example.
Stateless Version - ChangeNotifierProvider + Consumer
Here's a complete app example (copy paste into main.dart, replacing everything on page) using a StatelessWidget and the typical/common ChangeNotifierProvider & Consumer.
This version uses a mocked long duration async call when flipping Switch.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(ChangeNotifierProvider<DatabaseListenable>(
create: (context) => DatabaseListenable(),
child: MyApp())
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Provider Demo App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: StatelessSwitchProviderPage(),
);
}
}
class DatabaseListenable with ChangeNotifier {
bool active = false;
Future<void> setActive(bool value) async {
// Mock slow database call ↓
await Future.delayed(Duration(milliseconds: 500), () {
active = value;
print('Async DB call DONE.');
});
notifyListeners(); // ← causes Consumer to rebuild
}
}
class StatelessSwitchProviderPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Switch Provider Stateless'),
),
body: SafeArea(
child: Center(
child: Consumer<DatabaseListenable>(
builder: (context, db, child) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Active? ${db.active}'),
Switch(
onChanged: (val) {
print('Switch value: $val');
db.setActive(val);
},
value: db.active,
)
],
),
),
),
),
);
}
}
You have to add setState((){}); which rebuild the screen and display changes on your screen

How do I make the build function wait until a button is pressed in an alert in init?

I am trying to display an Alert that shows a disclaimer to the user as soon as the app is opened. The build method will run, that is the app will start its processing only after the user presses okay on the alert.
I've managed to show the alert in init using
SchedulerBinding.instance.addPostFrameCallback((_) => AlertWindow().showAlert(context));
or
Future.delayed(Duration.zero, () => AlertWindows().showAlert(context));
This shows the alert, but the app starts building in the background. I want the app to run/build only after OKAY button is pressed, and after the alert is popped.
Hey I implemented some code, you can try this code directly on dartPad Paste the code in this Editor
I used setState, if it is for real time project you can use Providers or bloc, for performance.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Widget viewHolder;
void initState() {
viewHolder = Container();
WidgetsBinding.instance
.addPostFrameCallback((_) => afterPostFrameCallBack());
super.initState();
}
afterPostFrameCallBack() {
_showDialog();
}
#override
Widget build(BuildContext context) {
return viewHolder;
}
Widget _buildView() {
return Container(child: Text('This is after okay button'));
}
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: Text("App Update Available"),
content: Text(
"We have fixed some issues and added some cool features in this update"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
FlatButton(
child: new Text("ok"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
viewHolder = _buildView();
});
},
),
],
);
},
);
}
}

How to send and receive arguments between views in flutter

I'm trying to send data from a page to another in flutter project, tried all methods I found in another question but all failed, here is my code :
The first page :
Navigator.pushNamed(context, '/line_details', arguments: {'line':line,});
The second page:
class _LineDetailsState extends State<LineDetails> {
Map data = {};
#override
Widget build(BuildContext context) {
data = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
title: Text("$data"),
),
);
}
}
note: the line is a custom object created.
the error : always returns null, even when tried to send a single string as {"test": "test string"} it returns a null too
your Example is working fine
route in MaterialApp
routes: { "/line_details": (context) => LineDetails(), },
Push on FlatButton:
onPressed: () => Navigator.pushNamed(context, '/line_details', arguments: {'line':'test',}),
class LineDetails
class LineDetails extends StatefulWidget {
#override
_LineDetailsState createState() => _LineDetailsState();
}
class _LineDetailsState extends State<LineDetails> {
Map data = {};
#override
Widget build(BuildContext context) {
data = ModalRoute.of(context).settings.arguments;
print(data);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
title: Text("$data"),
),
);
}
}
with GestureDetector is also working
new GestureDetector(
onTap: () => Navigator.pushNamed(context, '/line_details', arguments: {'line':'test',}),
child: new Container(child: new Text("GestureDetector"),),
),
try this:
Navigator.push(
context,
LineDetailsState(
builder: (context) => LineDetailsState(
line: line
)))
and
class LineDetailsState extends StatefulWidget {
LineDetailsState(this.line);
Line line;
#override
_LineDetailsState createState() => _LineDetailsState();
}
class _LineDetailsState extends State<LineDetails> {
Map data = {};
#override
Widget build(BuildContext context) {
data = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
title: Text("$data"),
),
);
}
}
instead of "Line" replace it with name of your custom object
Your example looks proper one other thing you can try is typecast your argument
like below which help dart for linting. Note this is not related that you are getting null
class LineDetails extends StatefulWidget {
#override
_LineDetailsState createState() => _LineDetailsState();
}
class _LineDetailsState extends State<LineDetails> {
Map data = {};
#override
Widget build(BuildContext context) {
data = ModalRoute.of(context).settings.arguments as Map<String,object>;
print(data);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
title: Text("$data"),
),
);
}
}

How to call Layouts in flutter?

I'm new in Flutter.
I have a question
How to call layouts in flutter ?
I've been create some layouts that contains a lot of widget.
It's not right if I make every code inside 1 file.
so I decide to put the code for the widgets in every 1 layouts file.
and I dont know how to call them in the home-page.dart that I create.
I mean, if I push THIS (i.e page1.dart), then the page1.dart is appear.
thought that file (page1.dart) is in other directory (not inside lib dir).
I dont know. am I should use ROUTES ?
but I dont know how.
would you like to teach me ?
..............
here are. I have TabBar like this in my home_page.dart:
import 'package:flutter/material.dart';
import 'package:coba/second.dart';
class HomePage extends StatelessWidget {
static String tag = 'home-page';
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
title: "MySampleApplication",
home: new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Hello Flutter App"),
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: "First Tab"),
new Tab(text: "Second Tab"),
new Tab(text: "Third Tab"),
],
),
),
body: new TabBarView(
children: <Widget>[
new Text("You've Selected First"),
new SecondWidget(),
new ThirdWidget(),
]
)
),
)
);
}
}
class SecondWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
second(data: 'Hello there from the first page!'),
),
}
}
class ThirdWidget extends StatelessWidget {
#override
Widget build(BuildContext ctxt) {
return new Column(
children: <Widget>[
Text('halooo'),
Container(
color: Colors.black,
width: 200,
height: 200,
)
],
);
}
}
thank you so much
You can use any name that you want (generally, we have seen xxxScreen.dart or xxxPage.dart, but it is totally up to you).
Import your "destiny" page using in "origin" page using import:
import 'package:myproject/myPageScreen.dart';
Flutter offers 3 options:
Using Navigator:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
SecondPage(data: 'Hello there from the first page!'),
),
Using Named routes:
Declare you routes in MaterialApp:
MaterialApp(
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
);
And then use named route with Navigator:
onPressed: () {
Navigator.pushNamed(context, '/second');
}
Using onGenerateRoute:
Declare this property on your MaterialApp:
return MaterialApp(
// Initially display FirstPage
initialRoute: '/',
onGenerateRoute: _getRoute,
);
And create your route generator :
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) =>
FirstPage());
case '/second':
// Validation of correct data type
if (args is String) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
}
You can create your router as another file to help to organize your project.

Categories

Resources