send data through arguments with GetX - flutter

is it a good practice to send data with arguments in flutter using GetX (or any other way)? i mean is it good for performance and ram capcity ? ... like this example:
Get.toNamed(AppPages.servicesDetails, arguments: [service]);
when (service) contain a data for only one product came from API : like
(id, name, info, image ...etc).
and in the servicesDetails page:
final s = Get.arguments[0];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text(s.name),),

You can also used parameters.
var data = {
"email" : "test#gmail.com",
"message" : "hi!"
};
Get.toNamed(YourRouteName.name, parameters: data);
Also from getting it from another pages is like this.
print(Get.parameters['email']);
Also on Getx you can pass it like a url link on data as the document written.
https://github.com/jonataslaw/getx/blob/master/documentation/en_US/route_management.md
If you want to pass whole item data,
You can also pass a model from list if have onTap function though you need to decode it again
e.g
MyCardItemFromList(
name: list[index].name,
ontapFunction: () => Get.toNamed(
YourRouuteName.name,
parameters: {
/// Lets assume this is the item selected also it's own item data
"itembyIndex": jsonEncode(list[index]),
}
),
),
from controller
class MyXampleController extends GetxController{
//declare the model
final Rx<Model> model = Model().obs;
#override
void onInit() {
convertToDecode();
super.onInit();
}
convertToDecode(){
final decode = jsonDecode(Get.parameters['itembyIndex'])
final passToModel = Model.fromJson(decode);
model(passToModel);
// or if you are using getBuilder
// try do it like this
// model.value = passToModel;
// update();
// don't forget to call update() since it's needed from getbuilder;
}
}
Now, For calling the data from ui will be like this
final controller = Get.put(MyXampleController());
///// this will be the data we got from the item
controller.model.value.name

You can simply use arguments
Get.toNamed(
'/my-route',
arguments: "Hello",
);
on the second screen, you can do
final title = Get.arguments as String;

There is another way of accessing values on another page
By accessing the Controller
ControllerName obj = Get.find();
obj.function();
obj.variable;
obj.anythingYouWantToAccess;
& use it like it's on the current Controller
NOTE: The Controller you want to access should b open in simple words it should be the previous screen.

Related

Flutter GetX, call a controller method after all widgets has been built

I am using GetX and I have following codes.
RxList<Outlets?> listPharmacyOutlets = <Outlets>[].obs;
RxList listOutletCoordinates = [].obs;
#override
void onInit() async{
super.onInit();
await getPharmacyOutlets();
}
Future getPharmacyOutlets() async{
Map params = {
//some parameters
}
var res = await CommonApiProvider.getPharmacyOutlets(params)
var outlets = res.data;
int idx = 0;
listPharmacyOutlets.clear();
for(final outlet in outlets){
listPharmacyOutlets.add(Outlets(
"latitude": outlet.latitude,
"longitude": outlet.longitude,
"pharmacyId": outlet.id,
"outletName": outlet.name,
"address": null
));
//now populating address list to fetch addresses for all outlets
listOutletCoordinates.add({
"index": idx,
"latitude": outlet.latitude,
"longitude": outlet.longitude,
});
idx++;
}
//I cannot call getOutletAddresses() here because view will not be populated unless this completes.
}
Future getOutletAddresses() async {
await Future.forEach(listOutletCoordinates, (item) async {
var result = await CommonApiProvider.getOutletAddresses(item);
//from result update "address" in "listPharmacyOutlets" by the help of index property in item
});
}
Here is my view.
SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: List.generate(controller.listPharmacyOutlets.length, (index) {
var item = controller.listPharmacyOutlets[index];
return Container(
child: .......
);
})
),
);
What I want to do is, first method getPharmacyOutlets() call only fetches outlets from Rest api and when list of outlets is completed rendering in list,
call getOutletAddresses() method to fetch address from google's geocoding service against the supplied latitude and longitudes. After address has been fetched, i will
update each row in the rendered list of outlets.
I am fetching addresses and outlets separately because I don't want user to wait for all results to come.
Problem, I am not sure when to call getOutletAddresses() api so that the addresses are fetched only after outlets has been rendered in the view.
I have in my mind that this could be achieved using WidgetsBinding like this.
WidgetsBinding.instance.addPostFrameCallback((_) {
// here call method to fetch address from google service.
});
But I am not sure where to use this because I am using GetX, which means all my pages are stateless
Any help or any other better Idea will help me a lot.
Thanks
The best approach is to use the worker functions provided by getx controller like:
ever - is called every time the Rx variable emits a new value.
everAll - Much like ever , but it takes a List of Rx values Called every time its variable is changed. That's it.
once - is called only the first time the variable has been changed.
For the present situation,
You can use once or everand based on the required condition you can then call your desired function
So the best time to call the getOutletAddresses() function is after listOutletCoordinates is filled .
And in my opinion it's not about the view rendering and then calling a function. Instead its to focus on the data being fetched sequentially. Because the View updation is taken care by Getx Builder or Obx() provided by Getx once the data is properly fetched
If using ever:
ever(listOutletCoordinates,(value){
// Based on the value you can call the getOutletAdresses() function
});
But with once you get more flexibility , Use something like:
once(listOutletCoordinates,(value){
// Based on the value you can call the getOutletAdresses() function
},condition:() => listOutletCoordinates.isNotEmpty);
Alternatively:
You can use listen property to call function based on listening value.
Example:
listOutletCoordinates.listen((val){
// Based on the value you can call the getOutletAdresses() function
});

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

reusable classes in flutter

I have a page like the following. This page has two buttons. One of them is sending the form to the relevant place. In my case to the instructor. But, this one is not important for now. The second button is the important one. Anyway, the second one is saving the form as a draft. So, I know I need to save the data that is entered, in SQLite. Then when he/she want to edit the form again I need to pull the data from SQLite then fill the relevant places. Here is the question. When I click to edit button. The page below will show up with the content that is filled before. However, for this task, it does not make sense to code again the screen below for the edit button. So I need to reuse the page below. How can I do it? Any idea. Is there any content that could be helpful? Please attach a link, video, etc. It doesn't matter. I just want to learn how to do it. I hope I could explain the situation. If any more information is required to understand the situation feel free to ask for it.
There are many ways to achieve what you need. But basically, you need to check whether there is saved data or not? If there is saved data then show it otherwise show an empty page. Simple example:
class MyPageData {
String firstField;
String secondField;
// ... fields with page info
}
class MyFormPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyForPageState();
}
class _MyForPageState extends State<MyFormPage>{
MyPageData _savedData;
#override
void initState() {
super.initState();
_savedData = loadDataFromDb();
}
#override
Widget build(BuildContext context) {
String myFirstField = "";
String mySecondField = "";
// other fields
if (_savedData != null){
myFirstField = _savedData.firstField;
mySecondField = _savedData.secondField;
// other fields
}
// render page with fields values above
}
}
Here MyPageData is a model of all the data on your page. So it's easier to work with. Also, this data is saved to db and restored from db in the future.
MyFormPage is a stateful widget for your form page. There is a loadDataFromDb method that is used to load saved data. Then in the build method, we check whether there is saved data or not. If there is data we filled initial values for all fields on our page and use those fields as initial values for the widgets from which our page is constructed. So if there is no data saved then _savedData will be null and all widgets will have empty initial values.
If you put your page in a widget like this
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: // your page here
)
}
}
you can open it in different locations.
_onButton1Click(BuildContext context){
// do something button 1 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}
_onButton2Click(BuildContext context){
// do something button 2 specific
final route = MaterialPageRoute(builder: (context) => MyPage());
Navigator.of(context).push(route);
}

HTTP call on screen load in flutter

We have a Features class that we are trying to fill when a screen loads. Its an http call that returns the object. Im struggling with how to do this. All of our http calls are done on a button click:
here is the call
Future<Features> getFeatureStatus(String userID) async {
Features _features;
final response =
await http.post('http://url/api/Features',
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'userID' : userID }));
_features = Features.fromJson(json.decode(response.body));
return _features;
}
When i try to call it at the top of the class I get errors and cant get to the values.
class FlutterReduxApp extends StatelessWidget {
static final User user;
static final Features features = getFeatureStatus(user.userId);
The error I get is -- "A value of type 'Future' can't be assigned to a variable of type 'Features'.
Try changing the type of the variable, or casting the right-hand type to 'Features'.dart(invalid_assignment)"
Im sure im doing something incorrect here but I havent done a screen load call yet.
The getFeatureStatus function is returning a Future<Features> while you're trying to read it as type Features in the stateless widget.
There are different ways to read the value but since you have a button, you could convert the widget into a StatefulWidget then use the onPressed function to read the value and update the state afterwards such as.
onPressed: () async {
features = await getFeatureStatus(user.userId);
setState((){
// do something
});
}
In this case, the value features cannot be a static final so you'll have to change it to Features features.
Edit based on comment:
You could also do this inside an initState:
Features features;
#override
void initState () {
super.initState();
_asyncMethod();
}
_asyncMethod() async {
features = await getFeatureStatus(user.userId);
setState((){});
}
so in the widget build method you could do:
return (features == null)
? CircularProgressIndicator()
: MyWidget(...); // where features is used.
getFeatureStatus(user.userId).than((features)
{
// you will get the features object
//you can work on that object
}
);
calling the getFeaturesStatus method in the initState() when using the statefull.
First thing first, this line static final Features features = getFeatureStatus(user.userId); will not work as you are trying to assign a type Future to the type Features.
The solution for this is to await the future so that it resolves and returns a Feature data type that satisfies your variable named 'features'.
This goes as follows: static final Features features = await getFeatureStatus(user.userId); but this has to be in a separate function which is explicitly defined with the async parameter.
This solves the error in the respect of code that you have written, but as you stated that you want this to load after the screen loads (Or technically, when the main widget is "mounted").
The solution for this logical aspect can be the use of this.mounted.
All widgets have a bool this.mounted property. It turns true when the buildContext is assigned.
In short, suppose you want to run a function after any widget is mounted/loaded, you can test it via
if(this.mounted){
//Whatever you want to do when the widget has been mounted...
}

How to return data when popping multiple screens?

I know I can return data to the previous screen by using
Navigator.pop(context, 'Value');
But in my case I need to pop multiple screens by using
Navigator.popUntil(context, ModalRoute.withName('/login'));
I wonder in this case how do I pass the data back to the corresponding widget?
Thanks in advance.
you can send DATA in few ways
as a Parameter
using Shared_Preferences
using Static Variables
Only for Current Session
if you just need the DATA for Current Session you can go for Static Variables
step 1 : Create a Class and have Static Variable in it.
class Globaldata{
static String value;
}
step 2 : Initialise variable by
Globaldata.value="some_value";
step 3 : use of variable
String assigned_value = Globaldata.value;
The flutter API does not have that feature and from this https://github.com/flutter/flutter/issues/30112 discussion, that feature is not on the table yet. A walkaround was suggested though using the Page API.
However, in my opinion, it is cleaner to use the provider package https://pub.dev/packages/provider as part of your app state management to keep the data you want and make it available to any screen of interest. Follow these steps to achieve that.
Add the provider to your pubspec.yaml. Check the link above to see detailed instructions.
Create a notifier class that extends ChangeNotifier class as shown below. ChangeNotifier class is part of the flutter API.
class MyDataProvider extends ChangeNotifier {
//define your private data field(s). I'm using int here.
int _mydata;
//define a getter
int get myData => _myData;
// define a setter
set myData(newData){
_myData = newData;
notifyListeners();
}
}
Wrap your uppermost widget (or the parent of the screens where you want to pass the data) with the provider and instantiate it. I'm using main here.
void main(){
runApp(
ChangeNotifierProvider(create: (context) => MyDataProvider()),
child: MyApp(),
)
}
Assuming you have five screens: Screen1, Screen2, ..., Screen5 and you want to navigate to screen5, do some operations and return to screen1 with some data. On 1st screen, define a local variable for myData and create an instance of your myDataProvider. When a button is pressed to start the navigation, wrap up the push navigation in an asynchronous call.
//Screen1
int currentLocalData = 78;
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
onPressed: () async {
//Assign localData to myData in the provider
myProvider.myData = currentLocalData; //calls the setter define in the provider.
await Navigator.push(context, MaterialPageRoute(
builder: (context) => Screen5()
));
//Retrieve myData from the provider and assign it to currentLocalData.
//This executes after navigating back from Screen5
currentLocalData = myProvider.myData;
}
Let assume in Screen5 you retrieved the data and added 100 to it. Your aim is to return to Screen1 and make use of the new data, i.e 178. Here you will instantiate the provider again in Screen5 and assign the value 178 to myData.
//Screen5
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
myProvider.myData += 100;
//Use navigation.popUntil
Navigation.popUntil(context, ModalRoute.withName('/Screen1'));
Say you have Screen A,Screen B, Screen C. If you want to pop till Screen A and pass some data. Here is what you have to do.
1. Navigate from Screen A to Screen B
Navigator.pushNamed(context, '/screenb')
.then((value) {
//you will get return value here
});
2. To pop till Screen A.
//add thi code in Screen C
var nav = Navigator.of(context);
nav.pop('refresh');
nav.pop('refresh');