Navigator and Provider conflict in flutter - flutter

sending data between screen using Provider with Navigator concept make a conflict
error after the run
The following ProviderNotFoundError was thrown building SecondRoute(dirty):
Error: Could not find the correct Provider above this SecondRoute Widget
To fix, please:
Ensure the Provider is an ancestor to this SecondRoute Widget
Provide types to Provider
Provide types to Consumer
Provide types to Provider.of()
Always use package imports. Ex: `import 'package:my_app/my_code.dart';
Ensure the correct context is being used.
https://www.ideone.com/xHXK5m

you can pass data like this put data in class
RaisedButton(
child: Text(‘Send data to the second page’),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
data: data,
)),
);
},
),
and recieve data like this
class SecondPage extends StatelessWidget {
final Data data;
SecondPage({this.data});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Constructor — second page’),
),
body: Container(
padding: EdgeInsets.all(12.0),
alignment: Alignment.center,
child: Column(
children: <Widget>[
Container(
height: 54.0,
padding: EdgeInsets.all(12.0),
child: Text(‘Data passed to this page:’,
style: TextStyle(fontWeight: FontWeight.w700))),
Text(‘Text: ${data.text}’),
Text(‘Counter: ${data.counter}’),
Text(‘Date: ${data.dateTime}’),
],
),
),
);
}

You can try placing whatever Provider value you have above the MaterialApp and thus the navigation or, when you push to a second page, provide the value again. Providers are scoped to widget trees, so this is expected behavior.
As an example, to pass a value to another route, you could do something like
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
final foo = 'foo';
Provider<String>.value(
value: foo,
child: NewPage(),
);
}));
},
And then just consume it like regular in the NewPage route.

Try this code:
MaterialApp(
title: 'Navigation Basics',
home: ChangeNotifierProvider(
builder: (context) => Data(),
child: FirstRoute(),
))
Delete ChangeNotifierProvider in FirstRoute

Related

Flutter showDialog

Can i showDialog inside a function without passing context?
void test(){
showDialog(context: context, builder: (_) => AlertDialog(
content: Column(
children: [
Row(
children: const [
Icon(Icons.check_circle, color: Colors.green,),
Text("Hi"),
],
)
],
),
));
}
Sorry i didn't explain very well, without passing context to function, not to showDialog
According to the doc (https://api.flutter.dev/flutter/material/showDialog.html) you can't, it's required.
the short answer is no, you can't.
the long answer:
first, the BuildContext is a object type, so in order to remove conflictions between the context property and the context value we're going to rename it to contextGotFromUI.
Note: contextGotFromUI here is just a BuildContext object sp we can rename it with whatever we want.
just to not get confused by the same names
void test(){
showDialog(context: contextGotFromUI, builder: (_) => AlertDialog(
content: Column(
children: [
Row(
children: const [
Icon(Icons.check_circle, color: Colors.green,),
Text("Hi"),
],
)
],
),
));}
the context property in the showDialog is required to set from it's implementation:
Future<T?> showDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
bool barrierDismissible = true,
// more code
the BuildContext is an important topic to understand in flutter, to show a dialog widget on top of the screen the user is actually navigating in and seeing at any time, the BuildContext is what tells to show it on top of the widget with that specific context, and not other screens.
As from the showDialog official documentation:
The context argument is used to look up the Navigator and Theme for the dialog. It is only used when the method is called. Its corresponding widget can be safely removed from the tree before the dialog is closed.
so in order to show a dialog from an external method, you need to pass a context that belongs to a specific widget, then use it in the showDialog:
void test(BuildContext contextGotFromUI){
showDialog(context: contextGotFromUI, builder: (_) => AlertDialog(
content: Column(
children: [
Row(
children: const [
Icon(Icons.check_circle, color: Colors.green,),
Text("Hi"),
],
)
],
),
));}
then from your UI where you're calling that method, pass it:
Widget build(BuildContext) {
// widgets
//...
onPressed: () {
test(context); // will show an expected dialog on the screen
}
}
Yes, you can but you have to create the function inside a stateful widget not in the normal classes.
in case you create the function in a normal class the context will be required!
void test(BuildContext context){
showDialog(context: context, builder: (_) => AlertDialog(
content: Column(
children: [
Row(
children: const [
Icon(Icons.check_circle, color: Colors.green,),
Text("Hi"),
],
)
],
),
));
}

The element type 'String' can't be assigned to the list type 'Widget'

I'm new to flutter, would like to do a List to display the image and title in Grid View Builder however I met an error. I also have to call out the list however not sure what's the issue for the error which I'm don't know how to fix. Error coming out on the code with "appItem.items[i].appImage". Much appreciate your help!
List<AppProfile> get items {
return [..._items];
}
My array(still have quite a lot however only take these two for example)
List<AppProfile> _items = [
AppProfile(
appID: "",
title: "Whatsapp",
appImage: "assets/images/Materials-03.png",
appLink: "",
),
AppProfile(
appID: "",
title: "Wechat",
appImage: "assets/images/Materials-04.png",
appLink: "",
),
]
in my builder/ display file
Widget build(BuildContext context) {
final appItem = Provider.of<AppLogic>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text("Your Social Media"),
),
body:
Container(
child: GridView.builder(
padding: EdgeInsets.all(30),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2 / 2,
crossAxisSpacing: 30, //spacing between column
mainAxisSpacing: 20,
),
itemCount: appItem.items.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {},
child: Column(
children: <Widget> [
**appItem.items[i].appImage**,
]
),
);
},
),
),
);
Error occurs
Error: Could not find the correct Provider above this SocialProfileScreen Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that SocialProfileScreen is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
However in my home page have floating button
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
// "Navigator.push" returns a Future and "onPressed" accepts void
//so cannot directly use "Navigator" but if using () =>
//simply means that executing "Navigator..."
onPressed: () =>
Navigator.of(context).pushNamed(SocialProfileScreen.routeName),
),
The error occurs because your appItem.items[i].appImage returns a String instead of a Widget. In this case, you need to wrap it with a Widget that displays an image.
However, you can't use a String, double, bool or other variables into a list which is the Widgets list.
So you should add an Image.assets() widget or another one (which displays images), and give your image path.
Like:
Image.asset(appItem.items[i].appImage);

Dialog state does not change after restarting (closing and opening again) in flutter

I have defined one button inside one StatelessWidget (This will have the bloc creation logic and injecting using bloc provider, ), on click of the button i am showing a dialog and passing the bloc instance to it, as shown in the code.
//EsignBloc is defined here in parent statelessWidget. Defined i.e. creating the bloc instance and passing through the BlocProvider. Removed the code for simplicity
//This listener will be called when Button defined inside statelessWidget will be clicked. this is responsible for showing the dialog.
void _onClickHere(
BuildContext context,
) {
final dialog = Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
elevation: 0.0,
backgroundColor: Colors.transparent,
child: _GetSignUsingOtpView(),
);
showDialog(
context: context,
builder: (_) => BlocProvider<EsignBloc>(
create: (_) => BlocProvider.of<EsignBloc>(context), // passing already created bloc to dialog
child: WillPopScope(
onWillPop: () => Future.value(false),
child: dialog,
),
),
barrierDismissible: false,
);
}
Pasting some code of _GetSignUsingOtpView()
class _GetSignUsingOtpView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<EsignBloc, EsignState>(builder: (context, state) {
return Container(
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(
AppConstants.borderRadius,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () => _closeDialog(context),
child: Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8),
child: Icon(
Icons.cancel,
color: AppColor.primaryDark,
size: SizeConfig.safeBlockVertical * 2,
),
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrimaryText(text: state.otp), // data does not change after closing and opeing dialog again
PrimaryText(text: state.remainingTime), // data does not change after closing and opeing dialog again
],
),
),
],
),
);
});
}
void _closeDialog(BuildContext context) {
Navigator.pop(context);
}
}
The problem that I am facing is whenever the dialog opens again after closing, it doesn't show the latest data from the bloc. The dialog just shows whatever previous data is in the bloc. Can someone point it out, where i am making the mistake?
The reason that your dialog is not showing new data is because you are creating a new instance of the bloc. Even though you say that you're not.
BlocProvider<EsignBloc>(
create: (_) => BlocProvider.of<EsignBloc>(context), // passing already created bloc to dialog
child: ...
In some cases, BlocProvider can be used to provide an existing cubit to a new portion of the widget tree. This will be most commonly used when an existing cubit needs to be made available to a new route. In this case, BlocProvider will not automatically close the cubit since it did not create it.
To not create a new instance, you need to use BlocProvider.value. You can read more about it here
BlocProvider.value(
value: BlocProvider.of<EsignBloc>(context),
child: ...,
);
Instead of creating a new instance, this will use the previous instance and provide it to the child widget and will NOT close the bloc when its done using it. This is handy for dialogs and navigation.

How to transition to a new screen instantly

I want to create a transition immediately from one screen to another (no animation). However isInitialRoute generates an error due to an update to the Router widget. The suggestion is to use onGenerateInitialRoutes but I am not sure how to implement it.
The flutter design doc recommends this:
Routes & RouteSettings
Currently, the only way to add a Route to the history stack without playing its entrance animation is to mark it as an initial route in its RouteSettings. The declarative API requires that routes can be added without animation at any time. This is useful when the route is covered by another route and playing the animation simply doesn't make sense. To support this, a didAdd method is added to the Route interface. This method is called by the Navigator instead of didPush when the Route should just be added without its regular entrance animation. To simplify things, this new method will also be used to bring the initial route on screen. This makes the RouteSettings.initialRoute parameter useless and it will be removed from RouteSettings. This is a minor breaking change.
This is the code that is generating the error on isInitialRoute:
import 'package:flutter/cupertino.dart'
show
CupertinoApp,
CupertinoButton,
CupertinoPageRoute,
CupertinoPageScaffold;
import 'package:flutter/widgets.dart'
show
BuildContext,
Center,
Column,
Navigator,
Route,
RouteSettings,
SafeArea,
Spacer,
Text,
runApp,
Widget;
Widget makeButton(BuildContext context, String routeName) =>
new CupertinoButton(
onPressed: () => Navigator.pushReplacementNamed(context, routeName),
child: Text('Go to \'$routeName\''),
);
Route generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'not-animated':
return new CupertinoPageRoute(
settings: RouteSettings(name: settings.name, isInitialRoute: true),
builder: (context) => CupertinoPageScaffold(
child: SafeArea(
child: Center(
child: Column(
children: [
Spacer(),
Text('This is \'not-animated\''),
makeButton(context, 'animated'),
Spacer(),
],
),
),
),
),
);
default:
return null;
}
}
void main() {
runApp(
CupertinoApp(
onGenerateRoute: generateRoute,
initialRoute: 'animated',
routes: {
'animated': (context) => CupertinoPageScaffold(
child: SafeArea(
child: Center(
child: Column(
children: [
Spacer(),
Text('This is \'animated\''),
makeButton(context, 'not-animated'),
Spacer(),
],
),
),
),
),
},
),
);
}
If You Want to just navigate your screen add this to your code
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => yoursecondscreenname()),
);
},

Flutter: How to handle using StreamProvider, FireStore and using Drawer

I have a problem in using 'StreamProvider' and having 'Provider.of' delivering the goodies. I have two interlinked issues. (1) is I can't get ProxyProvider to work (and most likely I am just misunderstanding it, and it may not be the solution). (2) The widget Drawer() (or the way I am using it) seems to block 'Provider.of'. 'StreamProvider' can't be found up the tree.
In MyApp() I initially had:
return MultiProvider(
providers: [
StreamProvider<User>.value(value: AuthService().user),
StreamProvider<UserData>.value(
value: DatabaseServices(uid: 'user.userUid').userData),
StreamProvider<QuerySnapshot>.value(
value: DatabaseServices(uid: 'user.userUid').allTasks),
StreamProvider<List<TaskDetails>>.value(
value: DatabaseServices(uid: 'user.userUid').userTasksByImportance),
StreamProvider<List<PropertyDetails>>.value(
value: DatabaseServices(uid: 'UuO2DO0JUVbVD0R1JqIclI7fprF3')
.userProperties),
],
child: MaterialApp(
theme: themeData,
home: Wrapper(),
routes: {
SettingsForm.id: (context) => SettingsForm(),
TaskList.id: (context) => TaskList(),
AddTask.id: (context) => AddTask(),
EditTask.id: (context) => EditTask(),
PropertyList.id: (context) => PropertyList(),
AddProperty.id: (context) => AddProperty(),
AddUnit.id: (context) => AddUnit(),
TestMarkdown.id: (context) => TestMarkdown(),
},
),
);
I tried to use ProxyProvider to supply a user.userUid from StreamProvider to the other StreamProviders below it but couldn't get anything to work.
Next in Wrapper() I tried this:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return Authenticate();
} else {
return MultiProvider(
providers: [
StreamProvider<UserData>.value(
value: DatabaseServices(uid: user.userUid).userData),
StreamProvider<List<TaskDetails>>.value(
value: DatabaseServices(uid: user.userUid).userTasksByImportance),
StreamProvider<QuerySnapshot>.value(
value: DatabaseServices(uid: user.userUid).allTasks),
StreamProvider<List<PropertyDetails>>.value(
value: DatabaseServices(uid: user.userUid).userProperties),
],
child: Home(),
);
}
}
}
Now this is below MaterialApp() which I didn't think was a good idea, but I could make user.user.Uid available to the other StreamProvider(s) and all worked. Provider.of in Home() and HomePage() (called by Home()) supplied the goodies.
Now Home() has a Drawer:
return Scaffold(
drawer: buildDrawer(context),
backgroundColor: Colors.blueAccent[50],
appBar: appBar,
body: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
alignment: Alignment.bottomCenter,
image: AssetImage('assets/property_returns_logo_drawn.png'),
fit: BoxFit.fitWidth),
),
child: HomePage(),
),
);
}
}
Drawer buildDrawer(BuildContext context) {
final user = Provider.of<User>(context);
final userData = Provider.of<UserData>(context);
final userProperties = Provider.of<List<PropertyDetails>>(context);
final double _minHeight = 40;
final double _maxHeight = 40;
return Drawer(
child: ListView(
padding: EdgeInsets.all(0),
children: <Widget>[
DrawerHeader(
and userProperties is available within the drawer itself, but any widget called from the drawer gives the error that it can't find a relevant StreamProvider for the Provider.of. If I reput the relevant StreamProvider back in MyAPP() the relevant StreamProvider can be found. But with no values from Firestore unless I fudge and for user.userUid hardcode a valid uid:. Obviously not a solution but works as far as the widgets called by Drawer is concerned.
What I am looking for is to understand how to use ProxyProvider, or if that is not the solution, how to structure my code. Maybe I can use StreamProviders (or StreamBuilders) in every widget called from Drawer but that doesn't feel right. Code is at https://github.com/sctajc/property_returns.
As of today, ProxyProvider doesn't work as the StreamProvider, so you cannot use it the way you want.
To answer why your Drawer has access to Provider.of<Foo> while the routes opened from it haven't, is because the routes are defined in your MaterialApp.
What does this mean? It means that while Drawer is below Wrapper and thus, has access to Provider.of<Foo>, the other routes are technically above the Wrapper widget.
TL:DR
The solution would be just creating one ProxyProvider of your DatabaseServices like this:
ProxyProvider<User, DatabaseServices>(
update: (_, user, __) => DatabaseServices(uid: user?.userUid),
),
And then, if you need to tap into the UserData, you just need to do:
Provider.of<DatabaseServices>(context).userData
This way you avoid creating 4 instances of the same object (DatabaseServices) and at the same time, you solve your problem. However, the logic of your app will need to change a bit to make it work, I am sure it won't be a problem.
I hope this can help you and you can keep on developing your app!