hi I try using Getx Controller in flutter. I want my oninit of controller reload and set the new data each time user go two my certain page, but only the first time page reload oninint excute. how can I set onInit reload each time user go to this page?
my onInit code is:
#override
Future<void> onInit() async {
super.onInit();
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
name = (sharedPreferences.getString('name') ?? '-1').obs;
avatarImage = (sharedPreferences.getString('imageAddress') ?? '-1').obs;
username = sharedPreferences.getString('username') ?? '-1';
file = File(avatarImage.value);
}
Since the controllers aren't named, I will say that we have a ReloadedController which contains the onInit() in your code snippet, and we have the SpecificPageController that belongs to that specific page.
I can think of two solutions that will suit your case:
First sulution: delete the controller and inject it again, to execute the onInit():
class SpecificPageController extends GetxController {
#override
void onInit() {
Get.delete<ReloadedController>();
Get.put(ReloadedController());
super.onInit();
}
}
This will delete the ReloadedController from the memory, then inject it again, this will trigger the OnInit() to execute since we just injected it.
Second solution: forcefully execute the onInit() method:
class SpecificPageController extends GetxController {
#override
void onInit() {
Get.find<ReloadedController>().onInit();
super.onInit();
}
}
This will execute forcefully the OnInit() method, which will behave like a reload for your onInit() code every time the specific page will be opened.
Third, solution: using onGenerateRoute
return GetMaterialApp(
onGenerateRoute: (settings) {
if (settings.name == "/yourSpecificPageRoure") {
name = (sharedPreferences.getString('name') ?? '-1').obs;
avatarImage =
(sharedPreferences.getString('imageAddress') ?? '-1').obs;
username = sharedPreferences.getString('username') ?? '-1';
file = File(avatarImage.value);
}
},
// ...
Change /yourSpecificPageRoure with your route path name.
This method is called every time a route is generated in your app, the price of your code will be executed only when the route name is /yourSpecificPageRoure.
Related
I have a simple Widget with a ValueListenableBuilder that listens to a ValueNotifier.
The build function of the ValueListenablebuilder is never triggered when updating the value of the ValueNotifier from a native method call (using setMethodCallHandler).
Instead, if I use valueNotifier.listen(() {}), I'm able to receive new values even in the build function. The native code, in fact, emits a new String each second.
This is the minimal code to reproduce the issue:
class Main extends StatelessWidget {
Main({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
platform.invokeMethod("startNativeMethod");
platform.setMethodCallHandler(handleNativeMethod);
Future.delayed(Duration(seconds: 3)).then((value) {
// this WORKS and updates the Text!
resourceNotifier.value = "test";
});
resourceNotifier.addListener(() {
// this ALWAYS works (prints "test" and then native updates each second)
print(resourceNotifier.value);
});
return ValueListenableBuilder(
valueListenable: resourceNotifier,
builder: (context, String value, child) {
// this is triggered only with "test"
// (when updated from Future.delayed)
return Container(child: Text(value ?? "Empty"));
});
}
ValueNotifier<String> resourceNotifier = ValueNotifier(null);
MethodChannel platform = const MethodChannel("com.example.example");
Future<dynamic> handleNativeMethod(MethodCall call) async {
// this NEVER updates the Text() widget. Always triggers the listener instead.
resourceNotifier.value = "hello from native";
}
}
This goes also beyond ValueNotifiers. I've also tried moving to a Stateful Widget and setting state from handleNativeMethod. Even setState doesn't work from there.
Thanks in advance for the help!
I have a scopedModel class where I fetch data into it. The thing is that I'm not being able to render this data from InitState method using my scoped model where I have all my api requests. The method is being called but the inside callings are not, so my initial state of the page is not properly shown.
void initState() {
print("Check initState");
super.initState();
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
print("Get into the scoped model");
model.fecthCars();
model.fecthCities();
model.fecthBuys(model.getUserDto.token);
print(model.getBuys().length);
return;
});
}
None of the fetches(Api requests) get called. And the scopedModel returns a widget. I need this to be updated the first time I get into the manager and that's it. No need to call it again. Is this possible? or should I hardcode my api requests in each file I need?
UPDATE
If you have your scoped model class set up already you can set a Future like this inside of it
mixin MyModel on Model {
Future<TypeToReturn> methodName(String param) async {
Uri uri = new Uri.http('serverUrl', 'api/call');
return await http.get(uri).then((http.Response response) {
final List<dynamic> myResponse= json.decode(response.body);
return myResponse;
}).catchError((error) {
print(error);
});
}
}
Aftermards you can set up your FutureBuilder
Widget _buildBody(BuildContext context, MainModel model) {
return FutureBuilder(
future: model.methodName(someString), //This is the method name above
builder: (context, AsyncSnapshot<TypeToReturn> snapshot) { //type u return
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Center(
child: Text(
"No Data Found",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
),
),
);
return (create your own widget with the data inside the snapshot)
}
},
);
}
Hope this clarify things a little bit more on how I did it.
I stumbled upon the following solution:
In the State Class of my StatefulWidget I do:
#override
void initState() {
super.initState();
// and here...
MyModel model = ScopedModel.of(context);
// now I can do with the model whatever I need to do:
Text someVar = model.initialText;
model.getValuesFromSomewhere();
// and so on
}
This, in my opinion, is the easiest way of solving the problem as stated by the original Question.
I think you've a slight misunderstanding about the point of ScopedModel and ScopedModelDescendant. The basic idea of how these should work is that the ScopedModel is created with a valid model which can then be used in other parts of the app.
However, the ScopedModelDescendant should be used within the build() function of a one of your widgets and be part of the widget tree as well. The reason your fetch methods aren't being called is that it isn't in the widget tree so the build function will never be called.
I would recommend instead moving the fetch methods out of the model and into some other class (maybe call it a communicator or controller or something). Next, I'd make it so that the model is instantiated as the result of an asynchronous call from that controller.
And finally, rather than instantiating an invalid model then changing the model once the data has been fetched, I'd recommend using a FutureBuilder - this way you have control over what to build based on whether the future is underway, successful, or failed.
So that will look something like this (pseudo-code).
StatefulWidget (MyApp or whatever you call it)
build =>
FutureBuilder(<fetch model data>, ...)
(if done)
ScopedModel<MainModel>
.... (whatever your code has here)
ScopedModelDescendant<MainModel>
(build using the model)
(if not done)
Loading.... (if needed)
If you absolutely want your model to always be there, I'd still recommend doing the fetching in the top stateful widget and simply changing which model you pass in below it rather than modifying the existing model once the data is loaded.
this is my solution i hope it help
#override
void initState() {
super.initState();
User user = ScopedModel.of(this.context);
_controllerFirstName.text = user.userData['first_name'];
_controllerLastName.text = user.userData['last_name'];
}
As newbie in flutter it's very confusing for me when use setState in Flutter application. In below code boolean searching and var resBody used inside setState. My question is why only searching and resBody inside setState? Why not others variable?
var resBody;
bool searching = false,api_no_limit = false;
String user = null;
Future _getUser(String text) async{
setState(() {
searching = true;
});
user = text;
_textController.clear();
String url = "https://api.github.com/users/"+text;
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept":
"application/json"});
setState(() {
resBody = json.decode(res.body);
});
}
According to the docs:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
So if the state of the widget changes you have to call setState to trigger a rebuild of the view and see immediatly the changes implied by the new state.
Anyhow the below snippets are equivalent.
first case (directly form flutter create <myproject>):
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
second case:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
_counter++;
setState(() {});
}
What I don't know is the reason why and if the first case is the conventional way to use setState, I would say because of readability of code.
When you change the state of a stateful widget, use setState() to cause a rebuild of the widget and it's descendants.
You don't need to call setState() in the constructor or initState() of the widget, because build() will be run afterwards anyway.
Also don't call setState() in synchronous code inside build(). You shouldn't need to cause a rerun of build() from within build().
If you look at the implementation of setState:
void setState(VoidCallback fn) {
assert(fn != null);
assert(...);
final dynamic result = fn() as dynamic;
assert(...);
_element.markNeedsBuild();
}
you see that the only things it does are: asserting a few things to help you debug incorrect usage of it, executing the callback, and marking the element so it gets rebuild.
So, technically, it doesn't matter if you change some variables inside the setState callback or outside of it, as long as setState is called.
However, for readability there is a big difference. Rebuilding widgets has impact on the performance of the app, so you want to do so as little as possible. Making all, and only those, changes to variables that require the widget to rebuild inside the setState callback makes it clear to people (including your future self) exactly why a rebuild is needed.
When you need to change the value any widget shows on the screen. For example, in the app there was a task. After completion of which points should be added to the "wallet". But the problem is that we need to refresh the app to see points on the "wallet". To solve this we use Setstate() on Button Onpressed()
For example:
RaisedButton(
onpressed(){
setstate(){
points+10;
}
}
)
Every time the button is pressed it will refresh the widget with the new value returned by the "wallet" variable without the need to restart the entire App.
Whenever you want to update a widget tree (generally with some new data), you call setState. It can only be used in State class. Here's the simple implementation:
class _MyPageState extends State<MyPage> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => setState(() => _count++),
child: Text('Count = $_count'),
),
),
);
}
}
setState() is only being used with statefulWidget insides flutter. setState() tell the flutter to rebuild the page when something defined inside the setState() changes.
NOT: setState() is a callback function.
Text(questions[questionIndex])
Here I want to change the Text according to the given questions array, and on every button click, I want to increase the index by 1.
void answerQuesion() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
So, I have to put this index increment inside the setState(), so that flutter will rebuild the page after every change of questionIndex.
I'm just getting into Android Development using Xamarin with Visual Studio 2015 Update 3. I'm trying to create a simple spinner, but something is going very, very wrong with the order of execution in the debugger.
In shared code, I have an options class that contains common values for dropdowns, etc.
public interface IOptionsCache : INotifyPropertyChanged
{
// is not observable collection on purpose, the entire list is replaced
// when data is fetched from the server
IList<string> States {get;}
}
Also in shared code, I have a standard base type to implement INotifyPropertyChanged more easily.
public abstract class NotifyDtoBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
comparer = comparer ?? EqualityComparer<T>.Default;
if (comparer.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then the actual OptionsCache looks like
public class OptionsCache : NotifyDtoBase, IOptionsCache
{
protected IList<string> _states;
public IList<string> States
{
get { return this._states; }
set { SetField(ref this._states, value); }
}
public async Task PopulateCacheAsync()
{
// TODO: fetch options from server
// for now, populate inline
this.States = new List<string>(){ "MI", "FL", "ME", ... }
}
}
These are working just fine. So, in my Activity, I new up an options class, and try and populate a drop down, but things go very wrong.
[Activity (Label = "Simple App", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
protected IOptionsCache OptionsCache;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// new OptionsCache has no options to start with
// need to call PopulateCache to get data
var options = new OptionsCache();
// When the list is populated, populate the spinners
options.PropertyChanged += (sender, args) => PopulateSpinners();
// HERE IS WHERE THINGS TO BAD!
// As I step thru the code, when I hit this line (pre-execution) and then F10 to step over, it drops into the PopulateSpinners method and this.OptionsCache == null
this.OptionsCache = options;
// populate the cache async and let the property changed event
// populate the spinners
options.PopulateCacheAsync();
}
protected void PopulateSpinners()
{
Spinner statesSpinner = FindViewById<Spinner>(Resource.Id.StatesSpinner);
// this.OptionsCache == null
ArrayAdapter<string> departureAdapter = new ArrayAdapter<string>(this, global::Android.Resource.Layout.SimpleSpinnerDropDownItem, this.OptionsCache.States.ToArray());
statesSpinner.Adapter = departureAdapter;
}
}
It seems like my assignment call to the class variable is being skipped and the next method is being invoked. Not sure if it has to do with being an async method or what's going on here...
Well, the good news is that the code does actually run correctly... when it is actually deployed to the emulator.
It would seem that the latest code does not always deploy to the emulator and you may end up debugging 'old' code. One way to tell this is happening, is that the debugger will indicate invalid lines as the next statement (eg, it may say that a blank line or a class definition is the next statement, which is obviously not correct).
https://forums.xamarin.com/discussion/45327/newest-version-of-code-not-always-deployed-when-debugging-from-xamarin-studio
I noticed that MvxMessenger subscriptions were being invoked multiple times due to multiple instances of the same ViewModel. I read a bit about unsubscribing and disposing the tokens (which works and prevents multiple invocations) but I wanted to see the ViewModel being garbage collected naturally and the Messenger subscription along with it.
I wanted to set up a test project for Android similar to this one https://github.com/slodge/MessengerHacking. So here are the two ViewModels
public class FirstViewModel : MvxViewModel
{
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
private MvxCommand _showSecond;
public ICommand ShowSecond {
get {
_showSecond = _showSecond ?? new MvxCommand(() => ShowViewModel<SecondViewModel> ());
return _showSecond;
}
}
}
And
public class SecondViewModel : MvxViewModel
{
private readonly IMvxMessenger _messenger;
private readonly MvxSubscriptionToken _token;
public SecondViewModel(IMvxMessenger messenger) {
_messenger = messenger;
_token = _messenger.Subscribe<MyMessage> ((message) => {
Debug.WriteLine("incoming message");
});
}
private MvxCommand _send;
public ICommand Send {
get {
_send = _send ?? new MvxCommand(() => _messenger.Publish (new MyMessage (this)));
return _send;
}
}
private MvxCommand _garbageCollect;
public ICommand GarbageCollect {
get {
_garbageCollect = _garbageCollect ?? new MvxCommand(() => GC.Collect ());
return _garbageCollect;
}
}
}
Then I just have two MvxActivities bound to these ViewModels. If I go to SecondViewModel and click send I see the subscribed event fire off once. If I go back and forth between the First and SecondViewModel these event subscriptions build up and clicking Send fires each of them. Clicking GarbageCollect doesn't seem to make any difference (I'm hoping to see it invoked only once after clicking this).
It feels as though when I click the back button from SecondViewModel, once the MvxActivity is destroyed then SecondViewModel should be eligible for garbage collection.
Another thing I noticed is that even if I Subscribe without saving it into a token, the behaviour is the same. The only way I have successfully got the events to stop firing is by saving the token and calling Unsubscribe or Dispose on the token, however it feels like SecondViewModel is still not getting garbage collected in this case.
Could this be something to do with the recent changes to Xamarin.Android? Or is there something I just don't get!
Many thanks
I know it is too late for answer, but for the sake of reference:
Short Answer:
In view(MvxActivity), handle DestroyCalled event like this:
DestroyCalled += (s, e) =>
{
if (ViewModel is IDisposable)
(ViewModel as IDisposable).Dispose();
};
In viewmodel, implement IDisposable interface:
public new void Dispose()
{
base.Dispose();
//Unsubscribe messages here
}
Long Answer:
http://slodge.blogspot.co.uk/2013/11/n42-is-my-viewmodel-visible-can-i-kill.html