Replacing MvxCachingFragmentCompatActivity and the Backstack - mvvmcross

I've been upgrading an MVVMCross project to v5.6.3
Mostly there, but I have a question. I've read and digesting this SO answer.
In my old code, which inherited from the MvxCachingFragmentCompatActivity class, I had the following method overload:
public override void OnBeforeFragmentChanging(IMvxCachedFragmentInfo fragmentInfo, Android.Support.V4.App.FragmentTransaction transaction)
{
var currentFrag = SupportFragmentManager.FindFragmentById(Resource.Id.content_frame) as MvxFragment;
if (currentFrag != null && fragmentInfo.ViewModelType != typeof(MenuViewModel)
&& currentFrag.FindAssociatedViewModelType(typeof(MainViewModel)) != fragmentInfo.ViewModelType)
fragmentInfo.AddToBackStack = true;
base.OnBeforeFragmentChanging(fragmentInfo, transaction);
}
Is this responsibility of adding a fragment to the backstack now handled by MVVMCross? Or is there a replacement method which I am meant to overload?
My observation is that the fragments are being added to the backstack, as evidenced by the previous fragment loading when I tap the back button.
Just wanted clarification on that if possible.
Cheers

Related

Skip subtree in Listener ANTLR4

Is there any Way to skip the parsing of specific block while using Listener in ANTLR4 using enter or exit method.
I have read link here but unable to make it work.
Thank You!
By the time you're using the Listener pattern with your own Listener class, the input is already correctly lexed and parsed. Therefore, the answer to your question is no. When you're using the listener you're typically just walking the tree post-parse.
Does that mean all is lost though? Of course not. All you have to do is simply not code the Enter or Exit events for those constructs you want to "ignore." It's that easy.
As to if-else statements, I've always implemented them using the visitor pattern like this:
As to how to program an if statement, I'll give you a peek at they way I implement them:
public override MuValue VisitIfstmt(LISBASICParser.IfstmtContext context)
{
LISBASICParser.Condition_blockContext[] conditions = context.condition_block();
bool evaluatedBlock = false;
foreach (LISBASICParser.Condition_blockContext condition in conditions)
{
MuValue evaluated = Visit(condition.expr());
if (evaluated.AsBoolean())
{
evaluatedBlock = true;
Visit(condition.stmt_block());
break;
}
}
if (!evaluatedBlock && context.stmt_block() != null)
{
Visit(context.stmt_block());
}
return MuValue.Void;
}
Granted, this probably doesn't make much sense out of context, but rest assured it works. To see this in its full context, please visit Bart Kiers for an excellent example of grammar and implementation .

MvvmCross ViewModel lifecycle during rotation

I have been using Mvvmcross to develop Android application. I am dealing with the issue of ViewModel lifecycle during a rotation. It seems that generally ViewModel is preserved during a rotation. However this is not the case when I present ViewModels in MvxTabActivity. When the rotation happens it always calls a ViewModel constructor.
I have used similar code structure as in N+1 tutorial https://github.com/slodge/NPlus1DaysOfMvvmCross/tree/master/N-25-Tabbed.
Is there a way to modify this tutorial to keep ViewModels in memory during rotation when using MvxTabActivity?
The default ViewModel caching which attempts to workaround the Android rotation behaviour is based around IMvxSingleViewModelCache - so it's not too surprising it can't cope with multiple Activities and multiple ViewModels.
For where this interface is declared and used, see https://github.com/slodge/MvvmCross/search?q=IMvxSingleViewModelCache&ref=cmdform
If this behaviour is troubling you, then you should be able to work around it by one of :
1. Use fragment based tabs rather than Activity based ones
Android handles Fragment lifecycle differently to Activity ones.
2. Or continue using activity based tabs, but implement your own IMvxSingleViewModelCache
It should be simple, for example, to identify your child view models with by their 'Child' naming convention.
With this done you can then implement something like:
public class MyCustomViewModelCache
: IMvxSingleViewModelCache
{
private const string BundleCacheKey = "__mvxVMCacheKey";
private int _counter;
private IMvxViewModel _currentViewModel;
public void Cache(IMvxViewModel toCache, Bundle bundle)
{
if (toCache != null
&& toCache.GetType().Name.StartsWith("Child"))
{
// don't worry about caching child view models
return;
}
_currentViewModel = toCache;
_counter++;
if (_currentViewModel == null)
{
return;
}
bundle.PutInt(BundleCacheKey, _counter);
}
public IMvxViewModel GetAndClear(Bundle bundle)
{
var storedViewModel = _currentViewModel;
_currentViewModel = null;
if (bundle == null)
return null;
var key = bundle.GetInt(BundleCacheKey);
var toReturn = (key == _counter) ? storedViewModel : null;
return toReturn;
}
}
This class based on MvxSingleViewModelCache.cs with just one small addition.
You can register an instance of this class as the IMvxSingleViewModelCache singleton during the InitializeLastChance of your Setup.
Mvx.RegisterSingleton<IMvxSingleViewModelCache>(new MyCustomViewModelCache());
With this done, the home/tab activity should (I think) continue to work - and it'll pass the viewmodels down to the tab children after rotation.
(Other possibilities for IMvxSingleViewModelCache are possible - e.g. it could cache multiple view models - but please don't let it cache too many view models for too long or you may run into 'out of memory' conditions)
3. Or switch the Android rotation handling off
If you add the android:configChanges="orientation" flag (or it's monodroid equivalent Attribute) then you can just handle the rotation yourself.

Overriding WP8 Navigation - crash in PhoneApplicationPage

I'm trying to do something that's arguably a bad idea, but I think it's still possible. I'm trying to override how WP8 handles the Back Button and implement it myself. I theorize that if I:
The Plan
Only ever create one "Frame" and "Page" in the entire application
Always handle PhoneApplicationPage.BackKeyPress myself unless they were about to back out of the application.
The Repro
Here's a sample project that has the crash
The code
..then it should work. However, my attempts are being thwarted by Windows Phone. Here's the code:
// This basically happens on PhoneApplicationService.OnLaunched
_viewModelChanged.StartWith(ViewModel).Where(x => x != null).Subscribe(vm => {
var page = default(IViewFor);
var frame = RootVisual as PhoneApplicationFrame;
// Find the initial PhoneApplicationPage for the app
page = RxApp.GetService<IViewFor>("InitialPage");
// Depending on how we're being signalled (i.e. if this is cold start
// vs. resume), we need to create the PhoneApplicationFrame ourselves
if (frame == null) {
frame = new PhoneApplicationFrame() {
Content = page,
};
}
page.ViewModel = vm;
var pg = page as PhoneApplicationPage;
if (pg != null) {
pg.BackKeyPress += (o, e) => {
if (ViewModel.Router.NavigationStack.Count <= 1 ||
ViewModel.Router.NavigateBack.CanExecute(null)) {
return;
}
e.Cancel = true;
ViewModel.Router.NavigateBack.Execute(null);
};
}
// Finally, set Application.RootVisual
RootVisual = frame;
});
Sadness
This works great, until right after this code executes, where a DispatcherItem queued by the framework crashes the app:
System.NullReferenceException occurred
Message: A first chance exception of type 'System.NullReferenceException' occurred in Microsoft.Phone.ni.dll
Microsoft.Phone.ni.dll!Microsoft.Phone.Controls.PhoneApplicationPage.InternalOnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) Unknown
Microsoft.Phone.ni.dll!Microsoft.Phone.Controls.PhoneApplicationPage.Microsoft.Phone.Controls.IPhoneApplicationPage.InternalOnNavigatedFromX(System.Windows.Navigation.NavigationEventArgs e) Unknown
Microsoft.Phone.ni.dll!System.Windows.Navigation.NavigationService.RaiseNavigated(object content, System.Uri uri, System.Windows.Navigation.NavigationMode mode, bool isNavigationInitiator, Microsoft.Phone.Controls.IPhoneApplicationPage existingContentPage, Microsoft.Phone.Controls.IPhoneApplicationPage newContentPage) Unknown
Microsoft.Phone.ni.dll!System.Windows.Navigation.NavigationService.CompleteNavigation(System.Windows.DependencyObject content, System.Windows.Navigation.NavigationMode mode) Unknown
Microsoft.Phone.ni.dll!System.Windows.Navigation.NavigationService.ContentLoader_BeginLoad_Callback(System.IAsyncResult result) Unknown
Microsoft.Phone.ni.dll!System.Windows.Navigation.PageResourceContentLoader.BeginLoad_OnUIThread(System.AsyncCallback userCallback, System.Windows.Navigation.PageResourceContentLoader.PageResourceContentLoaderAsyncResult result) Unknown
Microsoft.Phone.ni.dll!System.Windows.Navigation.PageResourceContentLoader.BeginLoad.AnonymousMethod__0(object args) Unknown
[Native to Managed Transition]
mscorlib.ni.dll!System.Delegate.DynamicInvokeImpl(object[] args) Unknown
System.Windows.ni.dll!System.Windows.Threading.DispatcherOperation.Invoke() Unknown
System.Windows.ni.dll!System.Windows.Threading.Dispatcher.Dispatch(System.Windows.Threading.DispatcherPriority priority) Unknown
System.Windows.ni.dll!System.Windows.Threading.Dispatcher.OnInvoke(object context) Unknown
System.Windows.ni.dll!System.Windows.Hosting.CallbackCookie.Invoke(object[] args) Unknown
System.Windows.RuntimeHost.ni.dll!System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(System.IntPtr pHandle, int nParamCount, System.Windows.Hosting.NativeMethods.ScriptParam* pParams, System.Windows.Hosting.NativeMethods.ScriptParam* pResult) Unknown
So, I've solved this - my code was problematic because I didn't grok how WP8 works :) Here's what I understand now, which may also be wrong but I'll write it anyways
How your WP8 app is initialized:
The OS creates your App class via rehydrating App.xaml.cs
This means, your constructor gets run, and as part of that, you create a PhoneApplicationFrame
Creating a PhoneApplicationFrame seems to also set a global static variable (same thing happens with creating PhoneApplicationService in the App.xaml, it sets PhoneApplicationService.Current).
NavigationService then attempts to recreate a XAML View via a resource string (i.e. '/MainPage.xaml'). Either it will recreate the one that was previously tombstoned, or if not, it defaults to the one in your WMAppManifest (this is the part I didn't understand).
PhoneApplicationFrame.Navigated gets called by NavigationService - this is where you can actually start doing stuff, including most importantly, setting Application.RootVisual, which will send the Loading... screen away
PhoneApplicationService.Launched or PhoneApplicationService.Activated finally fires, once basically everything is set up, depending how your app was woken up.
Found the issue. Well, the tip of the iceberg.
The code of the InternalOnNavigatedFrom method is:
internal override void InternalOnNavigatedFrom(NavigationEventArgs e)
{
PhoneApplicationPage content = e.Content as PhoneApplicationPage;
string str = ((content == null) || (content.Title == null)) ? string.Empty : content.Title;
PerfUtil.BeginLogMarker(MarkerEvents.TH_ONNAVIGATEDFROM_PAGE, string.Format("{0},{1},{2}", (base.Title == null) ? "" : base.Title, e.NavigationMode, str));
this.OnNavigatedFrom(e);
PerfUtil.EndLogMarker(MarkerEvents.TH_ONNAVIGATEDFROM_PAGE, string.Format("{0},{1},{2}", (base.Title == null) ? "" : base.Title, e.NavigationMode, str));
DeviceStatus.KeyboardDeployedChanged -= new EventHandler(this.OnKeyboardDeployedChanged);
Task rootTask = ApplicationHost.Current.RootTask;
rootTask.OnVisibleRegionChange = (ITask.VisibleRegionChanged) Delegate.Remove(rootTask.OnVisibleRegionChange, new ITask.VisibleRegionChanged(this.OnVisibleRegionChange));
Task task2 = ApplicationHost.Current.RootTask;
task2.OnSipVisibilityChange = (ITask.SipVisibilityChange) Delegate.Remove(task2.OnSipVisibilityChange, new ITask.SipVisibilityChange(this.OnSipVisibilityChange));
this._lastSipHeight = 0.0;
this._dictionary = null;
}
After a bit of debugging, I concluded that neither e or Application.Current.RootTask were null. After scratching my head, I looked at the code of the KeyboardDeployedChanged event handler:
public static event EventHandler KeyboardDeployedChanged
{
[SecuritySafeCritical] add
{
if (KeyboardDeployedSubscription == null)
{
KeyboardDeployedSubscription = new SubscriptionHandler(DeviceTypes.KeyBoard);
}
KeyboardDeployedSubscription.Changed += value;
}
[SecuritySafeCritical] remove
{
KeyboardDeployedSubscription.Changed -= value;
}
}
This code is poorly written. If the remove part of the handler is called before the add, KeyboardDeployedSubscription will be null and an exception will be raised. To test my theory, I subscribed to the event in App's constructor:
public App()
{
// Global handler for uncaught exceptions.
UnhandledException += Application_UnhandledException;
DeviceStatus.KeyboardDeployedChanged += (sender, e) => { };
And sure enough, the exception was gone. Now, to understand why your code is triggering this issue, I backtraced to which part of the framework is supposed to subscribe to the event. The only candidate is the InternalOnNavigatedTo method.
Therefore, your issue is that OnNavigatedFrom is called even though OnNavigatedTo was never called.
Since you are strungling with the built-in auto navigation of Windows Phone to the page defined in WMAppManifest.xml, I tried to remove the auto navigation and it basically worked (no exception).
I just replaced
<DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
with
<DefaultTask Name="_default" />
Not sure if this solves your problem but it at least doesn't crash anymore.

A function that deletes an instance by removing it from stage and nulling it does not remove it from memory

I have an issue with a function I use to delete an instance and replace it with another. Basically, it keeps the item in memory no matter what. Inside the object I have weak listeners and I null everything after it gets removed, but the function I run to check if it is still active tells me that it is (just an Event.ENTER_FRAME tracing some text, with a weak link).
Even when I removed everything from the instances I am loading, it still seems to stay in memory, according to my trace it still is. How do I completely delete something from memory more thoroughly than nulling it out after removing it from the stage? Am I not seeing something?
This is the function:
private function loadArea(inputArea:String)
{
//This is for a checker to make sure that areas only get loaded once.
currentRoom = inputArea;
//If the area currently loaded is not null, make it null now.
if(selectedArea != null) selectedArea = null;
//Null any data inside of the reference used to create the name of the new area.
areaReference = null;
//Grab the class using the input.
areaReference = getDefinitionByName(inputArea + "Area") as Class;
//Null the sprite used to house the class
areaSprite = null;
//Set the holder as a new instance of the desired class.
areaSprite = new areaReference() as Sprite;
//If the selected area is still not null for some reason,
if(selectedArea != null)
{
//Remove the area from the container...
areaContainer.removeChild(selectedArea);
//...and nullify it.
selectedArea = null;
}
//Set the current area as the newly created instance.
selectedArea = areaSprite;
//If the area is not the "Game", load in the assets one way,
if(inputArea != "Game") selectedArea.construct(areaAssets);
//otherwise do it another way.
else selectedArea.construct(newScreenData,apiServer,cdnServer,areaAssets);
//This is for a checker that fades out the screen, which it needs to fade back in soon.
newScreenData = null;
//While the container for areas has any areas inside of it, remove them.
while(areaContainer.numChildren) areaContainer.removeChildAt(0);
//...then add the new instance area to the container.
areaContainer.addChild(selectedArea);
//...then let all the parts of the game know that a new area has been laoded in.
Global.echoEvent.echo("gameBootUp","playAreaIn");
}
The memory is actually released when Garbage Collector will find and erase an orphaned instance of yours. Before that, your memory usage will state there is an instance in memory. There is no way to force garbage collection, calling System.gc() only "instructs" Flash to run it, it might not obey. So, you have done what you had to, let it be.
Removing all references including stage and nulling an object is all it takes to free up memory.
If the object is not being released then you are missing something or doing something incorrectly or out of sequence.
Carefully go through your code, making sure you identify where objects are being referenced so you can remove them.
Looking at your example code:
if(selectedArea != null) selectedArea = null;
Here you are making sure that the selectedArea is null.
But immediately after you are testing selectedArea again
(you know it is null so this block is never used)
if(selectedArea != null){
//Remove the area from the container...
areaContainer.removeChild(selectedArea);
//...and nullify it.
selectedArea = null;
}
In every language its VERY DIFFICULT to "clear" memory... even HTML. That being said... try to reduce your memory footprint.
Correct Null:
1. remove all event listeners
2. remove all children
3. remove all mapped/referenced methods/parameters
4. set the class object to null
In most cases this is all you need to do, the WAY you do it will determine if the object gets cleared. Consider the following situation.
You have a custom sprite class (MEMORY FOOTPRINT #1) that has a mapped property (mapping happen when one class object references another). Once you map/reference one object to another = MEMORY FOOTPRINT #2. Adding events = MEMORY FOOTPRINT #3, etc and so on.
Custom Sprite
import flash.display.Sprite;
class CustomSprite extends Sprite{
private var _mappedProperty:Object;
public function addMapping(map:Object):void{
_mappedProperty = map;
}
public function finalize():void{
_mappedProperty = null;
}
}
Assuming we're using CustomSprite in many other methods, lets look at some common ways of removing the ojbect.
INCORRECT - in this situation [objToRemove] was not set to null to free its memory:
var objToRemove:CustomSprite = new CustomSprite;
function doSomething(referenceObj:CustomSprite):void{
var methodObj:CustomSprite = referenceObj;
//CRAZY LINES OF CODE
methodObj = null; //frees memory from [doSomething::methodObj]
referenceObj = null; //frees memory from [doSomething::referenceObj]
//objToRemove is not cleared and will remain in memory
}
INCORRECT - in this situation [objToRemove] has a reference object so it will not clean until the reference is removed:
var objToRemove:CustomSprite = new CustomSprite;
var mappedObject:Sprite = new Sprite;
objToRemove.addMapping(mappedObject);
objToRemove.addEventListener(Event.ENTER_FRAME,onEnterFrame);
//CRAZY LINES OF CODE
//remove all children
while(objToRemove.numChildren > 0){
objToRemove.removeChildAt(0);
}
//remove all event listeners
objToRemove.removeEventListener(Event.ENTER_FRAME,onEnterFrame);
//this will NOT work
objToRemove = null;
//reason is objToRemove has a reference object of [mappedObject]
//[mappedObject] is not a child so it needs to be removed manually
//from WHITIN the CustomSprite class using [CustomSprite::finalize()]
Ok... breath... the correct way is actually simple.
CORRECT - here we are using [Dynamic] objects rather than [Static] class objects, this is considered Object Mapping:
//think of this as a global list of objects
var objPool:Dictionary = new Dictionary;
//create a pool reference
objPool['poolObj'] = new CustomSprite;
//CRAZY LINES OF CODE;
//do the normal [null] process
//both of these will work
objPool['poolObj'] = null;
//or
delete objPool['poolObj'];
SUPER ADVANCED CORRECT - no example provided, I have to get back to work lol...
1. Take a ByteArray clone of the class
2. User a Loader to construct the ByteArray as the class, rather than using "new"
3. When finished... unload/remove the loader
4. EVERYTHING will clear... thats how a Loader works!
(Not getting into why and how... too long of an explanation)
Although this works flawlessly... its not generally accepted or suggested in a work environment.

Remove ViewController from stack

In our App we have a log-in ViewController A. On user log-in, a request navigate is automatically called to navigate to the next ViewController B. However when this is done we want to remove the log-in ViewController A from the stack so the user cannot "go back" to the log-in view but goes back the previous ViewController before the log-in instead.
We thought about removing the ViewController A from the stack when ViewController B is loaded, but is there a better way?
In the Android version of the App we've set history=no (if I recall correctly) and then it works.
Is there an similar way to achieve this in MonoTouch and MvvmCross?
I ended up with removing the unwanted viewcontroller from the navigation controller. In ViewDidDisappear() of my login ViewController I did the following:
public override void ViewDidDisappear (bool animated)
{
if (this.NavigationController != null) {
var controllers = this.NavigationController.ViewControllers;
var newcontrollers = new UIViewController[controllers.Length - 1];
int index = 0;
foreach (var item in controllers) {
if (item != this) {
newcontrollers [index] = item;
index++;
}
}
this.NavigationController.ViewControllers = newcontrollers;
}
base.ViewDidDisappear(animated);
}
This way I way remove the unwanted ViewController when it is removed from the view. I am not fully convinced if it is the right way, but it is working rather good.
This is quite a common scenario... so much so that we've included two mechanisms inside MvvmCross to allow this....
a ClearTop parameter available in all ViewModel navigations.
a RequestRemoveBackStep() call in all ViewModels - although this is currently NOT IMPLEMENTED IN iOS - sorry.
If this isn't enough, then a third technique might be to use a custom presenter to help with your display logic.
To use : 1. a ClearTop parameter available in all ViewModel navigations.
To use this, simply include the ClearTop flag when navigating.
This is a boolean flag - so to use it just change:
this.RequestNavigate<ChildViewModel>(new {arg1 = val1});
to
this.RequestNavigate<ChildViewModel>(new {arg1 = val1}, true);
For a standard simple navigation controller presenter, this will end up calling ClearBackStack before your new view is shown:
public override void ClearBackStack()
{
if (_masterNavigationController == null)
return;
_masterNavigationController.PopToRootViewController (true);
_masterNavigationController = null;
}
from https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Touch/Views/Presenters/MvxTouchViewPresenter.cs
If you are not using a standard navigation controller - e.g. if you had a tabbed, modal, popup or split view display then you will need to implement your own presentation logic to handle this.
You can't: 2. RequestRemoveBackStep().
Sadly it proved a bit awkward to implement this at a generic level for iOS - so currently that method is:
public bool RequestRemoveBackStep()
{
#warning What to do with ios back stack?
// not supported on iOS really
return false;
}
from https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Touch/Views/MvxTouchViewDispatcher.cs
Sorry! I've raised a bug against this - https://github.com/slodge/MvvmCross/issues/80
3. You can always... Custom ideas
If you need to implement something custom for your iOS app, the best way is to do this through some sort of custom Presenter logic.
There are many ways you could do this.
One example is:
for any View or ViewModel which needs to clear the previous view, you could decorate the View or ViewModel with a [Special] attribute
in Show in your custom Presenter in your app, you could watch for that attribute and do the special behaviour at that time
public override void Show(MvxShowViewModelRequest request)
{
if (request.ViewModelType.GetCustomAttributes(typeof(SpecialAttribute), true).Any())
{
// do custom behaviour here - e.g. pop current view controller
}
base.Show(request);
}
Obviously other mechanisms might be available - it's just C# and UIKit code at this stage
I don't know about mvvm but you can simply Pop the viewcontroller (AC A) without animation and then push the new viewcontoller (AC B) with animation
From within AC A:
NavigationController.PopViewControllerAnimated(false);
NavigationController.PushViewController(new ACb(), true);