Passing variable value from state to another widget - android

I am struggling with one simple case and it would be lovely if someone could help here.
Let's say that I have some stateful widget. In its state I define a variable (might be of any type) and this variable is later changed through setState() method, where I dynamically assign its value based on some certain criteria. Evertyhing until this point is conducted within one class.
What if I would like to access the value of this variable from another class (totally different page) and if its value changes, rebuild it? Could you please also give me some examples?
Thanks in advance!

That's exactly why State MANAGEMENT exists.
To manage your state through your app.
There are many different options to follow
See:
https://flutter.dev/docs/development/data-and-backend/state-mgmt/options

You can use provider package in that case.
In yaml file add,
provider: ^4.3.2+4
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
Counter _counterProvider;
#override
void initState() {
super.initState();
_counterProvider = Provider.of(context, listen: false);
}
void updateCounter() {
_counterProvider.setCount();
}
#override
Widget build(BuildContext context) {
Counter _counterProvider = Provider.of(context);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
_counterProvider.count.toString(),
style: TextStyle(
fontSize: 22,
),
),
),
RaisedButton(
onPressed: updateCounter,
child: Text('Click'),
),
],
),
),
);
}
}
// class for storing data(Counter.dart)
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier { // create a common file for data
int _count = 0;
int get count => _count;
void setCount() {
_count++;
notifyListeners();
}
}

Related

Is there a way to use the same globalkey in multiple widgets?? in flutter

So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code

How to keep the old data in same state while emitting the same state with different data and render them separately using different blocBuilders in UI

I have a bloc called specialityBloc which will fetch doctors according to their speciality from firestore by delegating it to a repository.
The problem is when I want to fetch different doctors with different speciality and emit them from same state(fetchSuccessState).
I am able to fetch them separately from firestore but when It comes to rendering in the UI using different bloc builders(that listen to the same specialityBloc).It overrides the old data(which was there in the first Api call) and replaces it with the result of the subsequent api calls.
I want to keep the old the in the state and render it UI and also render the new Data below it(which was emitted from the same state).
Here is my Specialitybloc
SpecialityBloc() : super(InitialState()){
on<GetSpeciality>((event, emit) async {
emit(WaitingSpeciality());
try{
final data = await FirebaseRepo.getDoctorsBySpeciality(event.speciality);
data.fold((l){
emit(GetSpecialitySuccess(doctors:l));
}, (r){
});
}catch(e){
print(e);
}
});
}
}
abstract class SpecialityState extends Equatable {
}
abstract class SpecialityEvent {
}
class InitialState extends SpecialityState {
#override
List<Object> get props => [];
}
class WaitingSpeciality extends SpecialityState {
#override
List<Object> get props => [];
}
class GetSpecialitySuccess extends SpecialityState {
final List<DoctorModel> doctors;
GetSpecialitySuccess({required this.doctors});
#override
List<Object> get props => [doctors];
}
class GetSpeciality extends SpecialityEvent {
final String speciality;
GetSpeciality(this.speciality);
}```
This is the UI part
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:patient_app/ui/screens/home/home_bloc/popular_bloc.dart';
import 'package:patient_app/ui/screens/home/home_bloc/speciality_bloc.dart';
import 'package:patient_app/ui/widgets/gridViewLoading.dart';
import 'package:shimmer/shimmer.dart';
import 'package:patient_app/ui/screens/home/home_bloc/home_bloc.dart';
import 'package:patient_app/ui/widgets/custom_carousel.dart';
import 'package:patient_app/ui/widgets/search_bar.dart';
import 'package:patient_app/ui/widgets/square.dart';
import 'package:patient_app/ui/widgets/username.dart';
class Home extends StatefulWidget {
const Home({ Key? key }) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
super.initState();
context.read<HomeBloc>().add(GetUserInfo());
context.read<PopularBloc>().add(GetPopularDoctors());
context.read<SpecialityBloc>().add(GetSpeciality('paediatrics'));
context.read<SpecialityBloc>().add(GetSpeciality('gynaecologist'));
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left:20.0,right: 20,),
child: Column(
children: [
const SizedBox(height: 35,),
BlocBuilder<HomeBloc,HomeState>(
builder: (ctx,state){
if(state is Waiting){
return Align(
alignment: Alignment.centerLeft,
child: Shimmer.fromColors(
baseColor: Colors.amber,
highlightColor: Colors.grey[300]!,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.amber,
),
height: 20,width: 150,),
));
}
if(state is Success){
return UserName(name: state.data.name!);
}
else{
return Container();
}
},
),
CustomCarousel(slides: [
SizedBox(
width: double.infinity,
child: Image.network("https://cdn.pixabay.com/photo/2020/09/13/20/24/doctor-5569298_960_720.png",fit: BoxFit.cover,),
),
SizedBox(
width: double.infinity,
child: Image.network("https://cdn.pixabay.com/photo/2021/11/20/03/16/doctor-6810750_960_720.png",fit: BoxFit.cover,),
),
]),
const SearchBar(),
BlocBuilder<PopularBloc,PopularState>(builder: (ctx,state){
if(state is WaitingPopular){
return const GridViewLoading();
}
if(state is PopularDoctorsSuccess){
return Square(doctors: state.doctors,title: "Popular Doctors",);
}
return Container();
}),
BlocBuilder<SpecialityBloc,SpecialityState>(builder: (ctx,state){
if(state is WaitingSpeciality){
return const GridViewLoading();
}
if(state is GetSpecialitySuccess){
return Square(doctors: state.doctors,title: " Paediatrics",);
}
return Container();
}),
BlocBuilder<SpecialityBloc,SpecialityState>(builder: (ctx,state){
if(state is WaitingSpeciality){
return const GridViewLoading();
}
if(state is GetSpecialitySuccess){
return Square(doctors: state.doctors,title: "Gynaecologist",);
}
return Container();
})
],
),
),
);
}
}
if I got you correctly you are trying to save the old result that you got from the API to use it later on right ?
if so then :
you can try to make you var a global variable and then when the data comes you can assign the data you got in this variable to use it later on in another state like that
late final data;
//or the other way you can make data as a list here
//of the type object you are using ex Doctor or so..
SpecialityBloc() : super(InitialState()){
on<GetSpeciality>((event, emit) async {
emit(WaitingSpeciality());
try{
data = await FirebaseRepo.getDoctorsBySpeciality(event.speciality);
data.fold((l){
emit(GetSpecialitySuccess(doctors:l));
}, (r){
});
}catch(e){
print(e);
}
});
}
// now you can use your data variable that has the info with any other state or with whatever you want
and just add them to your list each time and then you can get back to whatever object you want from the list like maybe search for an a doctor using some ID and if it's not in the list you can fetch it and add it again to the list and here you can use the same state to render a different object, also try to remove the equatable from your code and try without it if you don't really need it.
If I am getting it right, the simplest and the right approach would be to have different events returning different states for each specialty, while acting on the same or different builders on the UI; based on buildWhen method of the bloc builder. Internally you can write a reusable method which does the API query, trigger it each time with a different event (specialty) and it can emit a respective state based on the method caller.
Benefits: better control on the UI and better testability.

type 'Null' is not a subtype of type 'Function'

I am new to Flutter. I am building a quiz app and have the following three dart files:
main.dart
import 'package:flutter/material.dart';
import './answer.dart';
import './question.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatefulWidget {
State<StatefulWidget> createState(){
return _MyAppState();
}
}
class _MyAppState extends State<MyApp>{
var _questionIndex = 0;
_answerQuestion(){
setState(() {
_questionIndex = _questionIndex + 1;
});
}
#override
Widget build(BuildContext context) {
var questions = [
{'questionText': 'What\'s your favourite color ?',
'answers': ['Red','Blue','White','Black']
},
{'questionText': 'What\'s your favourite Animal ?',
'answers': ['Dog','Rabbit','Tiger','Monkey']
},
{'questionText': 'What\'s your favourite Day ?',
'answers': ['Tuesday','Monday','Sunday','Friday','Wednesday','Saturday']
},
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Column(
children: [
Question(questions[_questionIndex]['questionText'] as String,
),
...(questions[_questionIndex]['answers'] as List).map((answer) {
return Answer(_answerQuestion(),answer);
}).toList()
],
)
),
);
}
}
question.dart
import 'package:flutter/material.dart';
class Question extends StatelessWidget {
final String questions;
Question(this.questions);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.all(10),
child:(
Text(
questions,
style: TextStyle(
fontSize: 25),
textAlign: TextAlign.center,)
),
);
}
}
answer.dart
import 'package:flutter/material.dart';
class Answer extends StatelessWidget {
final Function buttonHandler;
final String answer;
Answer(this.buttonHandler,this.answer);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: ElevatedButton(
child: Text(answer),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white)
),
onPressed: () => buttonHandler,
),
);
}
}
when I run the application on my android in Android studio, I get this error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY╞═══════════════════════════════════════════
The following _TypeError was thrown building MyApp(dirty, state: _MyAppState#7f7de):
type 'Null' is not a subtype of type 'Function'
The relevant error-causing widget was:
MyApp file:///C:/src/first_app/lib/main.dart:7:10
This:
onPressed: () => buttonHandler,
needs to be either:
onPressed: buttonHandler,
or
onPressed: () => buttonHandler(),
depending on whether your handler matches the required signature exactly.
In addition, this:
return Answer(_answerQuestion(),answer);
needs to be
return Answer(_answerQuestion,answer);
Generally speaking, you have mixed up calling a method and passing a method as a parameter a few times, you may want to get more familiar with it.
First, you must pass a function structure instead returning value from the function by calling it.
You declared this function below:
_answerQuestion(){
setState(() {
_questionIndex = _questionIndex + 1;
});
}
and passed the return value instead of function structure like below:
return Answer(_answerQuestion(),answer);
As you can see the return value of _answerQuestion() is Null.
Change your code like this.
return Answer(_answerQuestion,answer);
And you need to call the funcion in the Answer component.
onPressed: buttonHandler
or
onPressed: () => buttonHandler()
Your code is working fine try flutter clean

Flutter setState executing but not rerendering UI when setting parent stateless widget flag

My app has an introductory feature where it simply informs the user on an action to take, the issue is this help action text (Container(...)) does not get removed one the setState() function is called.
Logical overview of process:
-> `User launches app`
|-> `login`
|-> `show main UI (with help action if first time launch)`
|-> first time launch ? show help text : don't show
| User acknowledges help text, set in preferences
Below are some code snippets of the dart fragments
UiHomePage (main UI - this is the parent UI)
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
#override
Widget build(BuildContext context) {
Widget pageDashboardUser() {
...
// Notify UiComponentPartnerSelector if we should show help action text based on AppSharedPreferences().isFirstTap()
Widget middleBrowseCard() {
return new FutureBuilder(
builder: (context, snapshot) {
return UiComponentPartnerSelector(
_displayProfiles, snapshot.data);
},
future: AppSharedPreferences().isFirstTap());
}
var search = topSearch();
var selector = middleBrowseCard();
return Stack(
children: [search, selector],
);
return Scaffold(...)
}
This Widget displays a bunch of profiles with a base card, a text overlay, and a hint text component.
The main focus is showHint define in the constructur (true if the app is launched for the first time), showTapTutorial() which either returns the hint component or an empty container and finally the _onTap(Profile) which handles the onclick event of a card.
UiComponentPartnerSelector (sub UI - the help text is shown here
class UiComponentPartnerSelector extends StatefulWidget {
bool showHint;
final List<Profile> items;
UiComponentPartnerSelector(this.items, this.showHint, {Key key})
: super(key: key);
#override
_UiComponentPartnerSelector createState() => _UiComponentPartnerSelector();
}
class _UiComponentPartnerSelector extends State<UiComponentPartnerSelector> {
UiComponentCard _activeCard;
int _tappedImageIndex = 0;
Widget showTapTutorial() {
if (!widget.showHint) {
return Container();
}
return Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 32),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.touch_app,
color: Colors.black.withOpacity(0.6),
),
Text(
"Touch to view partner profile",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
)
],
),
);
}
#override
Widget build(BuildContext context) {
Color _standard = Colors.white;
//
// _cache = widget.items.map((e) => {
// e.imageUri.toString(),
// Image.network(e.imageUri.toString())
// });
Future _onTap(Profile e) async {
if (!widget.showHint) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => UiViewProfile(e)));
} else {
AppSharedPreferences().setFirstTap(false).then((value) {
setState(() {
widget.showHint = false;
});
});
}
}
UiComponentCard createComponentCard(Profile e) {
...
return UiComponentCard(
onTap: () {
_onTap(e);
},
wImage: Center(
child: Image.network(
e.profileImageLink.toString(),
fit: BoxFit.fill,
),
),
wContent:
// Center(
// child: UiTextLine(text: e.displayName),
// ),
Column(
children: [
topBasicInfo(),
Expanded(child: Container()),
showTapTutorial(),
Expanded(child: Container()),
bottomBio()
],
),
);
}
return Container(
child: Stack(...)
);
Problem:
When _onTap(Profile) is clicked and showHint is true.
What should happen:
What SHOULD happen next is AppSharedPreferences().setFirstTap(false) should set the initial tap flag to false, then when finished setState() including setting showHint to false, then rerendering the UI and removing the hint text container (found in showTapTutorial()).
What happens:
What infact happens is when _onTap() is called, it updates the preferences correctly, setState() is called and showHint == false and !widget.showHint in showTapTutorial() is true returning Container() BUT the UI itself doesn't rerender.
Thus after clicking this "button" for the first time, the UI remains (doesn't change). Clicking a second time executes the Navigator.of(context).push(MaterialPageRoute(builder: (context) => UiViewProfile(e))); part WHILE the action help text (tutorial) is still showing. If I click on the same card again
Am I missing something or doing something wrong?

Flutter - passing data from child to parent and to another child

I got a class Restrictions and 3 childs.
I want to pass data from CountryDropDown (and from FieldDropDown class which looks almost same, and from DateSelector class but thats maybe later) class to Restrictions and then to UserOffers (with updating List by tapping a button).
For now I can only define variable in Restrictions class and pass it like this. All on Strings.
Restrictions class:
String selectedCountry = "Argentina";
...
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20),
child: FieldDropDown(),
),
Container(
padding: EdgeInsets.only(top: 10),
margin: EdgeInsets.only(left: 20),
child: CountryDropDown(
),
...
Container(
child: UserOffers(
country: selectedCountry,
field: selectedField,
),
),
UserOffers class:
class UserOffers extends StatefulWidget {
final String country;
final String field;
UserOffers({
this.country,
this.field
}); ...
CountryDropDown class:
static String value;
static String selected;
#override
void initState() {
selected = _CountryDropDownState.selected;
super.initState();
} ...
... ]
.map((label) => DropdownMenuItem(
child: Text(label),
value: label,
))
.toList(),
onChanged: (value) {
setState(() => selected = value);
print(selected);
},
),
),
)
Thank you in advance.
It seems that your problem is related to the state management in Flutter. There are a few approaches that solve this issue, for example:
inherited widget
provider
bloc
redux
If you want to learn more check the official doumentation.

Categories

Resources