This question is a follow-up / "second attempt" to a previous question of mine.
I am building a cross-platform mobile application using MvvmCross framework,
and I would like to use the Messenger plugin to receive event notifications published from somewhere else in my code.
I have tried to add the subscription in the ctor as follows:
public class MyViewModel : BaseViewModel, IMyViewModel
{
private MvxSubscriptionToken _showMsgToken;
public MyViewModel ()
{
_showMsgToken = MvxMessenger.Subscribe<ShowMsg>(message => onShowNavigation(), MvxReference.Weak);
}
private void onShowNavigation()
{
//Do Stuff
}
}
Now when I navigate to this ViewModel everything works and notifications are received.
However, when I navigate away and back to this ViewModel, I can see that the Subscription is adding another entry to the MvxMessenger subscriptions property, resulting onShowAdsNavigation() firing twice for each new event.
So, How can I subscribe to events in the ViewModel?
Or maybe I need to find a way to Unsubscribe from events?
If you need to actively unsubscribe from messages, then you can do this by capturing lifetime events within your views and then using these to drive your viewmodel. This is your code - you can do what you like.
For some options on this, see ViewModel LifeCycle, when does it get disposed?
I generally don't bother with active management of subscriptions. Instead I rely on the fact that the View will be removed from the UI, and so it and its ViewModel will be removed from memory at some time afterwards. When this happens I know that the subscription management will then happen automatically - when the View and ViewModel get Garbage Collected, then the subscriptions will also shortly afterwards get cleaned up. I know that the weak referencing used within the Messenger will mean that the subscriptions will clean themselves up.
To prove this, try https://github.com/slodge/MessengerHacking - it has a button to force GC to occur.
If this isn't "good enough* for your app, then see "If you need to actively..." above.
Related
I want to create a controller based JavaFX GUI consisting of multiple controllers.
The task I can't accomplish is to pass parameters from one Scene to another AND back.
Or in other words:
The MainController loads SubController's fxml, passes an object to SubController, switches the scene. There shall not be two open windows.
After it's work is done, the SubController shall then switch the scene back to the MainController and pass some object back.
This is where I fail.
This question is very similar to this one but still unanswered. Passing Parameters JavaFX FXML
It was also mentioned in the comments:
"This work when you pass parameter from first controller to second but how to pass parameter from second to first controller,i mean after first.fxml was loaded.
– Xlint Xms Sep 18 '17 at 23:15"
I used the first approach in the top answer of that thread.
Does anyone have a clue how to achieve this without external libs?
There are numerous ways to do this.
Here is one solution, which passes a Consumer to another controller. The other controller can invoke the consumer to accept the result once it has completed its work. The sample is based on the example code from an answer to the question that you linked.
public Stage showCustomerDialog(Customer customer) {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(
new Scene(
(Pane) loader.load()
)
);
Consumer<CustomerInteractionResult> onComplete = result -> {
// update main screen based upon result.
};
CustomerDialogController controller =
loader.<CustomerDialogController>getController();
controller.initData(customer, onComplete);
stage.show();
return stage;
}
...
class CustomerDialogController() {
#FXML private Label customerName;
private Consumer<CustomerInteractionResult> onComplete
void initialize() {}
void initData(Customer customer, Consumer<CustomerInteractionResult> onComplete) {
customerName.setText(customer.getName());
this.onComplete = onComplete;
}
#FXML
void onSomeInteractionLikeCloseDialog(ActionEvent event) {
onComplete.accept(new CustomerInteractionResult(someDataGatheredByDialog));
}
}
Another way to do this is to add a result property to the controller of the dialog screen and interested invokers could listen to or retrieve the result property. A result property is how the in-built JavaFX dialogs work, so you would be essentially imitating some of that functionality.
If you have a lot of this passing back and forth stuff going on, a shared dependency injection model based on something like Gluon Ignite, might assist you.
I've used AfterBurner.fx for dependency injection, which is very slick and powerful as long as you follow the conventions. It's not necessarily an external lib if you just copy the 3 classes into your structure. Although you do need the javax Inject jar, so I guess it is an eternal reference.
Alternately, if you have a central "screen" from which most of your application branches out you could use property binding probably within a singleton pattern. There are some good articles on using singleton in JavaFX, like this one. I did that for a small application that works really great, but defining all of those bindings can get out of hand if there are a lot of properties.
To pass data back, the best approach is probably to fire custom Events, which the parent controller subscribes to with Node::addEventHandler. See How to emit and handle custom events? for context.
In complex cases when the two controllers have no reference to each other, a Event Bus as #jewelsea mentioned is the superior option.
For overall architecture, this Reddit comment provides some good detail: https://www.reddit.com/r/java/comments/7c4vhv/are_there_any_canonical_javafx_design_patterns/dpnsedh/
We have a windows phone 8 application in which we are using mvvm light having four , five views , and about same number of view models. One day we observed that the size of the application is increasing with usage and eventually reaches more than 100 mb and eventually crashes.After lot of testing what we are able to understand is that every time we navigate to a view , its instance is created and stored in the memory.It was observed that all the instances of the view and the view model are living in the memory and thus increasing the space on the ram. We also confirmed the same by defining finializer on view class and view model , on closing the application the finializer is called exactly the same number of times the page was navigated to. We are binding the datacontext of the view to respective view model in xaml. One of the main view has an ad control , so size increases very fast if user navigates to that view multiple times. How to resolve this issue. What I am unable to understand is the view should be destroyed once the user presses the back button, but this is not happening . Any help would be much appreciated.
We found a solution to this by adding below line of code to code behind.
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
Messenger.Default.Unregister(this);
if (e.NavigationMode == NavigationMode.Back)
{
DataContext = null;
}
}
What we are doing above is that we are unregisterring all the message handlers for the page and assigning the DataContext to null. In our case the datacontext was assigned in the xaml only and messenge handlers were registered in the OnNavigatedTo event of the page. But what is still unclear that on navigating back from the page , the page object should have died automatically . And should this line of code be added to all the mvvm light project pages and if so then why is it not common practice.
The reason you are leaking View memory is because you are in some way subscribing to events of the ViewModel from inside your Views. Either refactor those subscriptions to be WeakEvent subscriptions or remove them inside your OnNavigatedFrom
Use an IOC Container to maintain a single instance of all the ViewModels.
One of the options would be to use SimpleIoc that comes with MVVM Light.
Best tutorial to learn MVVMLight SimpleIoc
I've seen a lot of questions here related to the OnNavigatedTo method, but none of them seem to answer the basic question of, "At what point should I load data?" The documentation on MSDN doesn't explicitly answer this question, as far as I can tell.
If I need to load a list of data from the local database, which method is the most appropriate to use? Should I use the OnNavigatedTo method, or the Loaded event?
What I've been using so far is this pattern, which seems to work well:
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (NavigationMode.New == e.NavigationMode) {
var data = LoadData();
this.DataContext = data;
}
}
What this means is that for a new instance of a page, load the data synchronously. This also means that the page will not be rendered until the data has finished loading and the profiler complains that I'm using too much UI thread time.
An alternate approach is this pattern:
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (NavigationMode.New == e.NavigationMode) {
var data = await LoadData();
this.DataContext = data;
}
}
But with this pattern, it seems to me that navigation, and therefore page rendering may occur before I've loaded the data and set the DataContext, meaning unnecessary re-paints and what-not.
I usualy bind to a ViewModel directly in XAML. Then in OnNavigatedTo I trigger the view model to fetch its data async.
This allows me to show basic values from start (page title etc.). Then when I start fetching the data I can also activate a progressbar directly in the ViewModel and then remove it once the data is fetched.
I recommend you load your data asynchronously. OnNavigatedTo is one place where you can start the loading. If you're talking about a page that the user is almost certainly going to navigate to, then you may be able to start loading earlier.
I have a series of blog posts that look at how async has some friction with traditional OOP. There are a couple of posts that look at data binding in particular, e.g., asynchronous construction (the section on asynchronous initialization) and asynchronous properties (the section on data binding).
Just a few hours ago I announced the first stable release for my AsyncEx library, which includes the NotifyTaskCompletion types that you can use to kick off an asynchronous loading operation and have your view respond automatically (via data binding) when it completes.
But back to the core problem: you do have to show something while the data is loading. I recommend you do not consider this "unnecessary", but rather accept it as an opportunity to provide a better user experience. Think about what you want your app to look like on a slower phone or if there is an error loading the data. Any time there's I/O, design the "Loading..." and "Error" states as well as the "Loaded" state.
In mvvmcross v3 ViewModel
public class TimerViewModel : MvxViewModel
{
System.Timers.Timer timer;
public TimerViewModel()
{
timer = new System.Timers.Timer(500f);
timer.Elapsed += HandleTimerElapsed;
timer.Start();
}
void HandleTimerElapsed (object sender, ElapsedEventArgs e)
{
Debug.Log( "Time Elapsed" );
}
}
As MvxViewModel doesn't implement IDisposable, where should I put the following code ?
timer.Stop();
timer.Elapsed += HandleTimerElapsed;
I find that mvvmcross code have some MvxWeakEventSubscription, is it used to solve my problem ?
There's no easy universal way to know when to dispose the ViewModel - especially once you start mixing and matching ViewModel presentation styles to include navigations, tabs, splitviews, flyouts, fragments, lists, etc. and as you include more and more platforms
As a result of this, a couple of ways I have shut things like timers down in the past are:
1. Sometimes I have used a specialised interface on the ViewModel and I ensure this is called appropriately on each client.
For example, I have done some starting/stopping of 'page' level Views using:
OnPause/OnResume in Android
OnNavigatedTo/OnNavigatingFrom in Windows
ViewDidAppear/ViewWillDisappear in iOS
I have thought about adding this as a generalised pattern to do this (it is logged in https://github.com/slodge/MvvmCross/issues/74) - but so far I've not added this to v3 as I think it would lead to too much misunderstanding among users - it's better to let them to do this in the very few situations where it's needed.
Update: I have blogged about this and published a sample - see http://slodge.blogspot.co.uk/2013/11/n42-is-my-viewmodel-visible-can-i-kill.html
2. Sometimes I've just used Event Aggregation through the MvvmCross Messenger - and I've used it's inherent WeakReference-based messaging to make sure the ViewModel can be garbage collected when the view has finished with it.
An example of this is in the InternetMinute sample - which has a single 'Tick generation service' which ViewModels can connect to via messaging for updates - see:
the service - https://github.com/slodge/MvvmCross-Tutorials/tree/master/InternetMinute/InternetMinute.Core/Services/Tick
a ViewModel that consumes the service - via https://github.com/slodge/MvvmCross-Tutorials/blob/master/InternetMinute/InternetMinute.Core/ViewModels/HomeViewModel.cs
You might consider this slightly inefficient - as the Tick messages will be generated even if the ViewModel isn't present - but it's only a small inefficiency.
3. I've considered using more final events - things like OnNavigatingFrom(BACK) and 'onDestroy' and some 'final' detection on the UINavigationController delegates ... but I've not had a reason to do this 'for real' on any project yet.
What exactly will this addActionListener Do.....we we call button.addActionListener(this) what will happen
It basically adds this (the current object) to a list of objects that will be notified when the component has an action performed on it, such as a button being pressed.
It's a way of registering your interest in what is happening to the component and is useful in that you don't have to keep polling a component to check its status.
Your object (or class, really) simply implements the interface methods for listening (such as actionPerformed) and that method will be called for each event that happens.
The Java tutorials have a large variety of different articles on the various listeners that you're likely to use.