ViewModel event fires multiple times - xamarin.forms

I'm using MVVM Light for my application and I have also implemented the INavigationService for going back/for between pages.
So in a common scenario, it's like this
MainPage > Categories > Rounds > DataPage.
In the DataPage, I'm making a request to fetch the results and depending on the result returned from the callback I call the .GoBack() method to pop the current page from the stack and return to Rounds.
What I have noticed is that if I hit first the DataPage and the .GoBack() gets called and then tap on a different round the callback method will be fired twice, and if I go back and in again thrice, and continues like this.
Essentially this means that the .GoBack() will be called again and the navigation gets messed up.
I believe this has to do with not cleaning up the previous VM's, I tried changing this behavior with the UnRegister / Register class from SimpleIOC but no luck.

In the ViewModel class
public void UnsubscribeFromCallBack()
{
this.event -= method;
}
In the .xaml.cs page
protected override void OnDisappearing()
{
base.OnDisappearing();
PageViewModel vm = (this.BindingContext as PageViewModel);
vm.UnSubscribeFromCallback();
}

Related

Using CEFSharp ILifeSpanHandler interface to Handle Popups

I have an issue with handling popups. I have implemented ILifeSpanHandler and OnBeforeBrowse (amoungst others) from the IRequestHandler.
How do I know in the ILifeSpanHandler what URL is being called? I am unable to get it in either the OnAfterCreated or OnBeforePopup. Currently I see it first in OnBeforeBrowse.
I have no code as my question is a "How to". In OnBeforePopup I have checked targetUrl however it seems to be there for decoration as I have read that it is not implemented anyway. I have also looked at the browner/chromiumWebBrowser objects, browser and newBroswer seem to be nothing. One would expect in OnAfterCreated chromiumWebBrowser would return an object but it is nothing in my case.
I am testing with the following
Public Sub OnAfterCreated(chromiumWebBrowser As IWebBrowser, browser As IBrowser) Implements ILifeSpanHandler.OnAfterCreated
Try
Debug.Print(vbNewLine)
Debug.Print("OnAfterCreated")
Debug.Print(String.Concat("OnAfterCreated - MainFrame.Url "), browser.MainFrame.Url)
Debug.Print("OnAfterCreated")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
End Sub
And I have the following
Public Function OnBeforePopup(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetFrameName As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean, popupFeatures As IPopupFeatures, windowInfo As IWindowInfo, browserSettings As IBrowserSettings, ByRef noJavascriptAccess As Boolean, ByRef newBrowser As IWebBrowser) As Boolean Implements ILifeSpanHandler.OnBeforePopup
Try
Debug.Print(vbNewLine)
Debug.Print("OnBeforePopup")
Debug.Print(String.Concat("OnBeforePopup - targetUrl "), targetUrl)
Debug.Print(String.Concat("OnBeforePopup - browser.MainFrame.Url "), browser.MainFrame.Url)
Debug.Print(String.Concat("OnBeforePopup - chromiumWebBrowser.Address "), chromiumWebBrowser.Address)
Debug.Print("OnBeforePopup")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
Return False
End Function
I have seen different approaches in handling popups using ILifeSpanHandler interface. One approach that I've seen also here in Stack Overflow and was accepted as the correct answer to that particular question is to return true in the OnBeforePopup implementation of ILifeSpanHandler then pass the targetURL argument to a handler that creates the popup.
This approach is very unideal because you are destroying the link between the page that actually opened the popup and the popup itself. If you access via JavaScript the opener property of the window object inside the popup you would notice that it is null. And the page that opened the popup would never know that the popup was actually opened because returning true cancels the creation.
The other approach is to let Cef create the popup and the programmer just decides whether to show the browser as a popup window or a child to control (like in tabbed browsing). This is error-free and almost ideal. But the problem with this approach is that you are not able to listen to events such as FrameLoadStart, FrameLoadEnd, AddressChanged, TitleChanged, etc.
One approach that is tagged experimental by the Cef developers is to return a new IWebBrowser instance via newWebBrowser out parameter. This has so many many side effects. The page that opened the popup would, of course, recognize the popup as his although it was not the original browser (IBrowser) that it created. The page may just ignore it like btcclicks.com and in that case, there'd be no problem. But there are websites like drops.xyz that is so particular with his stuff and will discard everything that is not originally his. So this is a problem.
So what is the correct approach?
The ChromeWebBrowser control
Now I'm going to share with you an undocumented approach in handling popups. Speaking of ChromeWebBrowser control, it is very much of help that we know how it creates the webbrowser which, in reality, it doesn't. The control just hosts the webbrowser window handle. It has a private field called managedCefBrowserAdapter (ManagedCefBrowserAdapter class) that handles the actual creation of the web browser. ChromiumWEbBrowser implements the IWebBrowserInternal that has a method OnAfterBrowserCreated with a single parameter of type IBrowser. The control then invokes browser.GetHost().GetWindowHandle() to get the actual window handle (HWND) of the webbrowser it is being hosted. It is quite good.
The problem of the ChromeWebBrowser is that it won't have a constructor that accepts an IBrowser as an argument. It only has constructor that accepts HtmlString, string and IRequestContext arguments. These control waits for the
invocation of OnHandleCreated (a base class override) where it calls the managedCefBrowserAdapter.CreateBrowser after which it waits till its implementation of IWebBrowserInternal's OnAfterBrowserCreated is invoked.
Again, what is the approach that works?
Now, this approach that actually works is a product of long series of trial and error. One caution though is that I don't know why and how it works but I know it works.
First, I did not use ChromeWebBrowser. But I copied its code omitting the part where it creates .net control. In this case, I am targeting the browser's window handle (HWND) to be host by any object that exposes a HWND. Obviously I created a class (NativeCefWebBrowser) that uses the modified code. The ChromeWebBrowser orignal constructors were still there untouched becuase they are used to the create the parent webrowser. But I added one constructor that accept the following arguments: ICefBrowserParent parent (an interface I've created and IBrowser browser that receives the browser argument in the ILifeSpanHandler's OnBeforePopup. I also added a public method AttachBrowser that has a single parameter IBrowser that recieves the IBrowser argument in the ILifeSpanHandler's OnAfterCreated. It the browser that will be kept by CefNativeWebBrowser class.
Why didn't I keep the browser instance received form ILifeSpanHandler.OnBeforePopup but used the instance received from ILifeSpanHandler.OnAfterCreated when they are the same browser instance? This is one of those parts that I don't know why. One thing I noticed is that when I called browser.GetHost().GetWindowHandle() during ILiffeSpanHandler.OnBeforePopup, the first window handle I received was the different compared to when I invoked the method during ILifeSpanHandler.OnAfterCreatd. Because of that, I store the browser instance from the latter that I passed to the NativeCefWebBrowser.AttachBrowser for its safekeeping.
In the NativeCefWebBrowser(ICefBrowserParent parent, IBrowser browser) contructor, I set the private following fields to true: browsercreated, browserinitialized (chromewebbrwoser orginal fields) and isAttachingBrowser (added private field). You don't call the ManagedCefBrowserAdapter's CreateBrowser in this contructor in instead call its OnAfterBrowserCreated passing the browser instance. You don't much in this constructor as you will wait the ILifeSpanHandler implementor to pass you the browser instance it will receive during its OnAfterCreated method. Take note that when calling the ManagedCefBrowserAdapter's OnAfterBrowserCreated method, ManagedCefBrowserAdapter will still invoke IWebBrowserInternal implementation of OnAfterBrowserCreated that when happens you have to exit immediately when isAttachingBrowser is true as the following code will no sense no more.
After calling the NativeCefWebBrowser(ICefBrowserParent, IBroser) construct, you can normally set event listeners as you will normally do.
And that's it.
The following are parts of the code that I wrote
The ICefBrowserParent interface
public interface ICefBrowserParent
{
IntPtr Handle { get; }
Size ClientSize { get; }
bool Disposing { get; }
bool IsDisposed { get; }
bool InvokeRequired { get; }
IAsyncResult BeginInvoke(Delegate d);
object Invoke(Delegate d);
event EventHandler Resize;
}
As you would notice, the methods, properties and events in this interface are already implemented by the System.Windowns.Forms.Control class. So if you implementing this from class inhering Control class, you would not need to implement this anymore. This interface is only for non-Control class.
class NativeCefWebBrowser
{
public NativeCefWebBrowser(ICefBrowserParent, IBroser)
{
requestContext = browser.GetHost().RequestContext;
this.parent = parent; // added field
HasParent = true; // IWebBrowserInternal. I don't know what's this for
mustSetBounds = true; // added field
browserCreated = true;
isAttachingBrowser = true; // added field
InitializeFieldsAndCefIfRequired();
managedCefBrowserAdapter.OnAfterBrowserCreated(browser);
}
}
ILifeSpanHandler.OnBeforePopup(..., out IWebBrowser newWebBrowser)
{
CefNativeWebBrowser b = new CefNativeWebBrowser
(
parent, // defined else where
browser
);
// Attach event handlers
b.TitleChanged...;
newWebBrowser = b;
}
ILifeSpanHandler.OnAfterCreated(...)
{
((CefNativeWebBrowser)webBrowser).AttachBrowser(browser);
}

Updating UI BeginInvokeOnMainThread not reflecting immediately

I am showing activity indicator after clicking login button until redirecting the user to another page, to make them understand some progress is going on. But after clicking login button Activity Indicator is not shown immediately, it is shown after few seconds,
Why its so? To reduce that delay only I am putting activity indicator...
My Code:
async void loginButtonGesture_Tapped(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() =>
{
loadingPanel.IsRunning = true;
loadingPanel.IsVisible = true;
});
}
Does the method have to be async void? It seems like this particular scheduling anything on the main thread shouldn't need to be async. Try that to see if it changes anything. Also you could try to set breakpoints on the Device.BeginInvokeOnMainThread line, and the loadingPanel.IsRunning... line to see where the delay happens.
First of all, loginButtonGesture_Tapped() event handler is triggered by UI thread so you don't need to use Device.BeginInvokeOnMainThread(), it is already in UI thread. But since you used Device.BeginInvokeOnMainThread() here, the reason for the delay is because on Android, your code inside of BeginInvokeOnMainThread() is added to MainLooper's message queue,(your code is not executed immediately) and is executed when the UI thread is scheduled to handle its messages.
The detailed answer can be found in Xamarin document:
For iOS:
IOSPlatformServices.BeginInvokeOnMainThread() Method simply calls NSRunLoop.Main.BeginInvokeOnMainThread
public void BeginInvokeOnMainThread(Action action)
{
NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}
https://developer.xamarin.com/api/member/Foundation.NSObject.BeginInvokeOnMainThread/p/ObjCRuntime.Selector/Foundation.NSObject/
You use this method from a thread to invoke the code in the specified object that is exposed with the specified selector in the UI thread. This is required for most operations that affect UIKit or AppKit as neither one of those APIs is thread safe.
The code is executed when the main thread goes back to its main loop for processing events.
For Android:
Many People think on Xamarin.Android BeginInvokeOnMainThread() method use Activity.runOnUiThread(), BUT this is NOT the case, and there is a difference between using runOnUiThread() and Handler.Post():
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//<-- post message delays action until UI thread is scheduled to handle messages
} else {
action.run();//<--action is executed immediately if current running thread is UI thread.
}
}
The actual implementation of Xamarin.Android BeginInvokeOnMainThread() method can be found in AndroidPlatformServices.cs class
public void BeginInvokeOnMainThread(Action action)
{
if (s_handler == null || s_handler.Looper != Looper.MainLooper)
{
s_handler = new Handler(Looper.MainLooper);
}
s_handler.Post(action);
}
https://developer.android.com/reference/android/os/Handler.html#post(java.lang.Runnable)
As you can see, you action code is not executed immediately by Handler.Post(action). It is added to the Looper's message queue, and is handled when the UI thread's scheduled to handle its message.

Call a method in application scope (.Net)

I have a UserControl(uc) in my master page, and a method(MyMethod) inside uc that make some calculations.
protected void Page_Load()
{
If(!IsPostBack)
MyMethod();
}
private void MyMethod()
{
SomeCalculations..
}
Because my uc is in master page, i can see the uc in all my aspx pages. My aim is that as soon as a user login the application, run MyMethod() just once (in a thread) and do calculations in an infinite loop until the user logout or application (or browser) closed. Although the calculations are outside of the PostBack, MyMethod will be called more than one time.
Assume that I m in Page-1 and it s loaded first time, MyMethod() will bi called. After another page (Page-2) is loaded, MyMethod will be called again and I want to prevent it. Is there a way to do something like this:
if(LifeCycle of application resumes)
{
MyMethod()
}
You can store a flag in application state and use it in a condition. Something like this, perhaps:
// in Application_Start in Global.asax
Application["IsRunning"] = false;
then:
private void MyMethod()
{
if (!((bool)Application["IsRunning"]))
{
Application["IsRunning"] = true;
// your code
}
}
Note that the state of a web application isn't always stable or intuitive. It's really meant to be a request/response system and is at the mercy of the web server for managing resources. This may not be as reliable as you expect.
You might want to consider having a separate application, such as a Windows Service, for performing ongoing background tasks.

How to deal with FragmentPagerAdapter reusing Fragments?

So, I'm trying to use a ViewPager from Android's support v4 library, but there's some serious issues with how it (or FragmentPagerAdapter) deals with Fragments. For instance, I subclassed FragmentPagerAdapter to do the following:
public class MyPagerAdapter extends FragmentPagerAdapter
{
private ArrayList<Fragment> fragments = null;
private ArrayList<Data> data = null;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<Data> data)
{
super(fragmentManager);
this.data = data;
fragments = new ArrayList<Fragment>();
for(Data datum : data)
{
MyDataFragment fragment = new MyDataFragment();
fragment.setData(datum);
fragments.add(fragment);
}
}
#Override
public Fragment getItem(int i)
{
return fragments.get(i);
}
#Override
public int getCount()
{
return fragments.size();
}
}
Now, I thought this would be sufficient, and that I could go on and implement MyDataFragment using the onCreateView method that Fragments typically implement. But I ran into an interesting problem. When I would navigate away from the Activity, and then back to it, the ViewPager would appear blank. Somehow, it was reusing Fragments by calling findFragmentByTag, then simply not even calling getItem, etc. What's worse, the Fragment would get no onCreateView event. So I figured I could utilize the ViewPager's Fragment caching by moving my onCreateView code, which primarily grabs references to the various Views the fragment inflates, to onAttach. The only problem is, that during onAttach, MyDataFragment's getView method always returns null. All of the examples for Fragments online describe that onCreateView should have all of your view setup code. Ok, fine. But then, when I create a method like MyDataFragment.setSomeField(String value), I need to use a reference to a TextView. Since onCreateView doesn't always get called (like, when Fragments are magically recycled by FragmentPagerAdapter, for instance), it's better to grab that reference in onAttach. However, during onAttach, the root view for the Fragment is still null (probably because onCreateView wasn't called in the first place)! No additional events happen after that (with the exception of onActivityCreated, which has nothing to do with the Fragment itself), so there's no place to do setup code. How is this supposed to work? Am I missing something important here, or was the Fragment system designed by a monkey?
I'm not sure that this is the right use case for a FragmentPagerAdapter (it sounds more like something you'd want to do with a ListAdapter).
From the FragmentPagerAdapter docs:
Implementation of PagerAdapter that represents each page as a Fragment
that is persistently kept in the fragment manager as long as the user
can return to the page.
This version of the pager is best for use when there are a handful of
typically more static fragments to be paged through, such as a set of
tabs. The fragment of each page the user visits will be kept in
memory, though its view hierarchy may be destroyed when not visible.
This can result in using a significant amount of memory since fragment
instances can hold on to an arbitrary amount of state. For larger sets
of pages, consider FragmentStatePagerAdapter.
I'd consider switching to the FragmentStatePagerAdapter or perhaps a ListAdapter.
If you want the createView to be called it will have to be recreated each time (destroy the old fragment and create new ones), but again I don't think that's quite what you want.

Why does my ASP.Net static function's "context" crossover between user sessions?

I think I need some help understanding how static objects persist in an ASP.Net application. I have this scenario:
someFile.cs in a class library:
public delegate void CustomFunction();
public static class A {
public static CustomFunction Func = null;
}
someOtherFile.cs in a class library:
public class Q {
public Q() {
if (A.Func != null) {
A.Func();
}
}
}
Some ASP.Net page:
Page_Init {
A.Func = MyFunc;
}
public void MyFunc() {
System.IO.File.AppendAllText(
"mydebug.txt", DateTime.Now.ToString("hh/mm/ss.fff", Session.SessionID));
}
Page_Load {
Q myQ = new Q();
System.Threading.Thread.Sleep(20000);
mQ = new Q();
}
The idea is that I have a business object which does some operation based on a callback function at the UI level. I set the callback function to a static variable on Page_Init (in the real code version, in the Master page, if that makes a difference). I thought that every execution of the page, no matter what user session it came from, would go through that function's logic but operate on its own set of data. What seems to be happening instead is a concurrency issue.
If I run one user session, then while it is sleeping between calls to that callback function, start another user session, when the first session comes back from sleeping it picks up the session ID from the second user session. How can this be possible?
Output of mydebug.txt:
01/01/01.000 abababababab (session #1, first call)
01/01/05.000 cdcdcdcdcdcd (session #2, first call - started 5 seconds after session #1)
01/01/21.000 cdcdcdcdcdcd (session #1 returns after the wait but has assumed the function context from session #2!!!!!)
01/01/25.000 cdcdcdcdcdcd (session #2 returns with its own context)
Why is the function's context (meaning, its local data, etc.) being overwritten from one user session to another?
Each request to an asp.net site comes in and is processed on it's own thread. But each of those threads belong to the same application. That means anything you mark as static is shared across all requests, and therefore also all sessions and users.
In this case, the MyFunc function that's part of your page class is copied over top of the static Func member in A with every page_init, and so every time any user does a page_init, he's replacing the A.Func used by all requests.
Static data is shared among the entire application domain of your webapp.
In short, it's shared among all the threads serving requests in your webapp, it's not bound to a session/thread/user in any way but to the webapp as a whole.(unlike e.g. php where each request lives in its own isolated environment bar a few knobs provided - such as the session variable.)
I won't try to improve on the other answers' explanations of static members, but do want to point out another way to code around your immediate problem.
As a solution, you could make an instance-oriented version of your class A, store it in a page-level variable, and pass it to Q's constructor on page load:
public class MyPage: Page {
private A2 _a2;
// I've modified A2's constructor here to accept the function
protected Page_Init() { this._a2 = new A2(MyFunc); }
protected Page_Load() {
Q myQ = new Q(this._a2);
// etc..
}
}
In fact, if there's no pressing need to declare A2 earlier, you could just instantiate it when you create your instance of Q in Page_Load.
Edit: to answer the question you raised in other comments, the reason the variables are being shared is that the requests are sharing the same delegate, which has only a single copy of its variables. See Jon Skeet's The Beauty of Closures for more details.
One solution you might consider is using [ThreadStatic].
http://msdn.microsoft.com/en-us/library/system.threadstaticattribute(VS.71).aspx
It will make your statics per thread. There are cavaets however so you should test.
If you want the data to persist only for the current request, use HttpContext.Items:
http://msdn.microsoft.com/en-us/library/system.web.httpcontext.items.aspx
If you want the data to persist for the current user's session (assuming you have session state enabled), use HttpContext.Session:
http://msdn.microsoft.com/en-us/library/system.web.httpcontext.session.aspx

Resources