Flutter get data that was returned to Drawer from Navigator.pop() - flutter

I have the main/homepage widget of my app, let's call it home.dart.
Inside this widget, I have defined the drawer key in my Scaffold widget. The code for the Drawer object is in a separate file, navdrawer.dart.
home.dart
import 'navdrawer.dart';
. . .
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavDrawer(),
...
Now inside NavDrawer, I construct my Drawer widget which has a settings button, which links to the settings.dart screen.
Which I do like this:
navdrawer.dart
. . .
InkWell(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
print(result);
},
child: ListTile(
leading: Icon(
Icons.settings,
color: AppTextColor,
),
title: Text('Settings'))),
So now, when the user presses the back button on the settings page, the Navigator.pop() call will return the data I need to the result variable in navdrawer.dart.
But my problem is ... how do I get this data to my home.dart screen/state?

I'll suggest you to use provider, scoped_model or other state management techniques for this. The easiest (but also the worthless) solution would be to use a global variable.
However, there's a middle ground. For simplicity I'm using dynamic for the type of result, you'd better know what Settings return, so use that type instead.
Your home.dart file
class _HomePageState extends State<HomePage> {
dynamic _result; // Create a variable.
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavDrawer(
onResult: (result) {
_result = result; // <-- This is your result.
}
),
);
}
}
Add following in your navdrawer.dart:
class NavDrawer extends StatelessWidget {
// Add these two lines.
final ValueChanged onResult;
NavDrawer({this.onResult});
// Other code ...
}
Modify your onTap method inside navdrawer.dart file:
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
onResult(result); // <-- Add this line.
}

Please set parameters into the pop method.
Like
Navigator.pop(context,true)

Define 'Static' global variable in homepage screen/widget
Then call the variable from anywhere :
1- homepage :
Static String getData;
2- when returned to Drawer :
homepage.getData=value;
Navigator.pop();

Related

Is it possible to share and update one screen's reactive value in another screen without Provider?

So I have this block of code in a widget that navigates to another screen:
screen_one.dart
class ScreenOne extends StatefulWidget {
const ScreenOne({ super.key });
#override
State<ScreenOne> createState() => _ScreenOneState();
}
class _ScreenOneState extends State<ScreenOne> {
List<String> state = [''];
#override
Widget build(BuildContext context) {
return Column(
MaterialButton(
onPressed: () => Navigator.pushNamed(context, '/screen-two'),
child: Text('Click here.')
),
Text(state[0]),
);
}
}
screen_two.dart
class ScreenTwo extends StatelessWidget {
const ScreenTwo({ super.key });
#override
Widget build(BuildContext context) {
return Container();
}
}
Basically I need to pass the state variable from ScreenOne to ScreenTwo and then update it there (in ScreenTwo)
ScreenTwo needs to display the same thing as ScreenOne and add() a new item to the state list when some button is clicked which should show on both the screens.
Its just one simple List so I am trying to avoid using provider.
Is it possible to do though?
I'm currently just passing it through the Navigator:
screen_one.dart
Navigator.pushNamed(
context,
'/post-info',
arguments: state,
),
screen_two.dart
Widget build(BuildContext context) {
final List<String> post = ModalRoute.of(context)!.settings.arguments as List<String>;
// ...
}
first I want to recommend you when things go bigger and more complex, it's better to use a state management approach, However since you did say that you have only one List you can simply use a ValueNotifier, with ValueListenableBuilder:
// this should be outside widget classes, maybe in a custom-made class or just in a global scope.
ValueNotifier stateNotifier = ValueNotifier([""]);
now in the places you want to use that state, you can use ValueListenableWidget like this:
ValueListenableBuilder(
valueListenable: stateNotifier,
builder: (context, value, child) {
return Column(
children: [
Text('${state[0]}'),
MaterialButton(
onPressed: () {
Navigator.pushNamed(context, '/screen-two'),
},
child: Text('click'),
),
],
);
},
);
}
}
and any other place where you want to see that state get updates, you need to use ValueListenableWidget.
Now, for executing a method like add() on the List and notify the widgets, you need to assign a new value for it like this:
void addInTheList(String elem) {
List current = stateNotifier.value;
current.add(elem);
// this exactly what will be responsible for updating.
stateNotifier.value = current;
}
now, you can just call addInTheList and expect it to update in all of them:
addInTheList("Example");

ChangeNotifier inaccessible in grandchildren widget of where it was provided

I am trying to use flutter provider in order to carry my state down a widget sub-tree/route, and while it works for the direct child of the widget that provided the change notifier class, it does not for the next one in line.
As far as I understand, the change notifier class should be passed down. To be more specific, I am trying to access it through context.read() in a function being called in its initState function.
Am I doing something wrong?
The code below illustrates my code.
Where it class notifier is provided:
onTap: () {
// Select body area
context.read<Patient>().selectBodyArea(areas[index]);
// Open complaint list
FlowRepresentation flow = context.read<Patient>().getFlow();
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider.value(
value: flow,
child: const ChiefComplaintList()
)
)
);
}
Navigation to the problem widget in ChiefComplaintList:
onTap: () {
// Select complaint
context.read<FlowRepresentation>().selectComplaint(ccs[index]);
// Show factors
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AttenuationFactors())
);
}
Where I'm having trouble accessing the change notifier class:
void getData() async {
_nrFactors = await context.read<FlowRepresentation>().getAttenuationFactors();
setState(() {}); // rebuild widget with data
}
#override
void initState() {
super.initState();
print("Initiated Attenuation Factors Lists State");
getData();
}

Flutter convert part of QuizesBloc state to QuizState

I have a List<Quiz> objects and I added these to QuizesState(Bloc is QuizesBloc) and displaying the listview. If I click on any single Quiz I want to add that particular Quiz object to another state called QuizState(Bloc is QuizBloc). How to do this?
(1) Add a selectedQuizId to your QuizesState.
(2) In your QuizesView (with the list of quizzes) add a BlocListener, listening to changes in QuizesState and if selectedQuizId is not null.
(3) navigate to a quiz route and provide selectedQuizId as an arugument
(4) create your QuizPage like this
class QuizPage extends StatelessWidget {
const QuizPage({super.key});
static Route<void> route(String? quizId) {
return MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => BlocProvider(
create: (context)
{
return QuizBloc(
quizId: quizId,
repository: context.read<QuizRepository>()
)..add(QuizRequested());
},
child: const QuizPage(),
),
);
}
#override
Widget build(BuildContext context) {
return const QuizView();
}
}
(5) pick up the quizId upon initializing the QuizBloc

I Want to Add Widgets Dynamically with Provider

I'm using Provider package, and I want to dynamically add widgets to display.
I wrote the code like below, but the widgets doesn't show anything.
No errors have occurred.
// Contains widget and related data
class WidgetData {
Widget? child; // want to show this
String data1;
int data2;
}
class Model exteds ChangeNotifier {
List<WidgetData> widget; // I want to show all of this widget.child
void addWidget(Widget child) {
print("Called1") // "Called1"
var w = widgets.toList();
w.add(child);
widgetData = w;
notifyListeners();
}
}
class Example extends StatelessWidget {
#override
build (BuildContext context) {
return Scaffold(
body: Column(children: [
for(var i in context.watch<Model>().widget) i.child!;
]);
}
}
When the button pushed, context.read<Model>().addWidget(Text("test")) will be called.
But still doesn't show widgets.
// inside of build(BuildContext context)
FloatingActionButton(
onPressed: () => context.read<Model>().addWidget(Text("test")),
child: Icon(Icons.abc)
);
Of course I built the tree of provider in main.
void main() {
runApp(MultiProvider(
ChangeNotifierProvider<Model>(
create: (_) => Model()),
));
child:
....
}
It is discouraged to create and store Widgets outside of the build function in Flutter. Your provider should not provide Widgets, but the data needed to construct widgets, and you then construct the widgets inside the build method.
For example, if instead of a list of Widgets you had a list of Strings, then in the build method you convert that list of Strings easily into Text widgets like this: Column(children: stringlist.map((e) => Text(e)).toList())

Flutter Provider rebuilt widget before parent's Consumer

I have got a problem with the provider package.
I want to be able to clean an attribute (_user = null) of a provider ChangeNotifier class (it is a logout feature).
The problem is when I am doing that from a Widget that use info from this Provider.
My main app is like :
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AuthProvider(),
builder: (context, _) => App(),
),
);
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (_, auth, __) {
Widget displayedWidget;
switch (auth.loginState) {
case ApplicationLoginState.initializing:
displayedWidget = LoadingAppScreen();
break;
case ApplicationLoginState.loggedIn:
displayedWidget = HomeScreen();
break;
case ApplicationLoginState.loggedOut:
default:
displayedWidget = AuthenticationScreen(
signInWithEmailAndPassword: auth.signInWithEmailAndPassword,
registerAccount: auth.registerAccount,
);
}
return MaterialApp(
title: 'My App',
home: displayedWidget,
routes: {
ProfileScreen.routeName: (_) => ProfileScreen(),
},
);
});
}
}
My Provider class (simplified) :
class AuthProvider extends ChangeNotifier {
ApplicationLoginState _loginState;
ApplicationLoginState get loginState => _loginState;
bool get loggedIn => _loginState == ApplicationLoginState.loggedIn;
User _user;
User get user => _user;
void signOut() async {
// Cleaning the user which lead to the error later
_user = null;
_loginState = ApplicationLoginState.loggedOut;
notifyListeners();
}
}
My Profile screen which is accessible via named Route
class ProfileScreen extends StatelessWidget {
static const routeName = '/profile';
#override
Widget build(BuildContext context) {
final User user = Provider.of<AuthProvider>(context).user;
return Scaffold(
// drawer: AppDrawer(),
appBar: AppBar(
title: Text('Profile'),
),
body: Column(
children: [
Text(user.displayName),
FlatButton(
child: Text('logout'),
onPressed: () {
// Navigator.pushAndRemoveUntil(
// context,
// MaterialPageRoute(builder: (BuildContext context) => App()),
// ModalRoute.withName('/'),
// );
Provider.of<AuthProvider>(context, listen: false).signOut();
},
)
],
),
);
}
}
When I click the logout button from the profile screen, I don't understand why i get the error :
As I am using a Consumer<AuthProvider> at the top level of my app (this one includes my route (ProfileScreen), I thought it would redirect to the AuthenticationScreen due to the displayedWidget computed from the switch.
But it seems to rebuild the ProfileScreen first leading to the error. the change of displayedWidget do not seems to have any effect.
I'm pretty new to Provider. I don't understand what I am missing in the Provider pattern here ? Is my App / Consumer wrongly used ?
I hope you can help me understand what I've done wrong here ! Thank you.
Note : the commented Navigator.pushAndRemoveUntil redirect correctly to the login screen but I can see the error screen within a few milliseconds.
Your user is null, and you tried to get the name of him. You need to check it before using it. It will look like this:
user == null ?
Text("User Not Found!"),
Text(user.displayName),
From the provider API reference of Provider.of :
Obtains the nearest Provider up its widget tree and returns its
value.
If listen is true, later value changes will trigger a new State.build
to widgets, and State.didChangeDependencies for StatefulWidget.
So I think the line final User user = Provider.of<AuthProvider>(context).user; in your profile screen calls a rebuild when the _user variable is modified, and then the _user can be null in your ProfileScreen.
Have you tried to Navigator.pop the profile screen before clearing the _user variable?