Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin Android - How do I pass an event from MainActivity to ViewModel on Forms Page?

I have a Xamarin Forms app that I want to read NFC tags on. I've made an interface called INFC for reading the tags.

/// <summary>
/// This interface defines NFC relating functions that are cross platform.
/// </summary>
public interface INFC
{
    /// <summary>
    /// Inits the object.
    /// </summary>
    void Init();

    /// <summary>
    /// Starts the process for scanning for the included platform.
    /// </summary>       
    /// <param name="tagInformation">Optional related tag information that you may need for the scan.</param>
    void StartNFCScan(object tagInformation = null);

    /// <summary>
    /// Called when the tag is finished scanning and we have the content.
    /// </summary>
    event EventHandler<String> TagScanned;
}

I created the following Android specific implementation.

[assembly: Dependency(typeof(INFCImplementation))]
namespace Test.Droid.Models
{
/// <summary>
/// The android implementation of the NFC platform.
/// </summary>
public class INFCImplementation : INFC
{        
    public event EventHandler<String> TagScanned;
    public static NfcAdapter adapter { get; set; }

    /// <summary>
    /// Called to init the object.
    /// </summary>        
    public void Init()
    {
        //Set the adapter.
        adapter = NfcAdapter.GetDefaultAdapter(Forms.Context);
    }

    /// <summary>
    /// Starts the process for scanning for the included platform.
    /// </summary>       
    /// <param name="tagInformation">Optional related tag information that you may need for the scan.</param>
    public void StartNFCScan(object tagInformation = null)
    {
        //Create a variable to hold the tag content.
        String tagContent = null;

        try
        {                
            //Process the NDEF tag and get the content as a String.
            tagContent = "http://stackoverflow.com";
        }
        catch (Exception e)
        {
        }

        //Raise the tag content with the scanned event.
        TagScanned?.Invoke(this, tagContent);
    }
}

}

My Main Activity is as follows.

/// <summary>
/// The main activity for the app.
/// </summary>
[Activity(Label = "Test", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    INFCImplementation nfcImplementation;        

    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(bundle);

        //Enable experimental fast renderers.
        Forms.SetFlags("FastRenderers_Experimental");

        Forms.Init(this, bundle);

        //Load up the zxing framework.
        ZXing.Net.Mobile.Forms.Android.Platform.Init();

        //Load up the user dialogs plugin.
        UserDialogs.Init(() => (Activity)Forms.Context);

        //Init the tinted image renderer.
        TintedImageRenderer.Init();            

        //Store our NFC interface class.
        nfcImplementation = DependencyService.Get<INFCImplementation>() as INFCImplementation;

        //Init our NFC interface.
        nfcImplementation.Init();            

        LoadApplication(new App());
    }

    protected override void OnResume()
    {
        //Call the base method.
        base.OnResume();

        //Create the intent for NFC reading.
        Intent intent = new Intent(this, GetType()).AddFlags(ActivityFlags.SingleTop);

        //Start a dispatch on our NFC adapter.
        INFCImplementation.adapter?.EnableForegroundDispatch
        (
            this,
            PendingIntent.GetActivity(this, 0, intent, 0),
            new[] { new IntentFilter(NfcAdapter.ActionTechDiscovered) },
            new String[][]
            {
                new string[]
                {
                    "android.nfc.tech.Ndef"
                },
                new string[] {
                        "android.nfc.tech.MifareClassic"
                    },
            }
        );
    }

    protected override void OnPause()
    {
        //Call the base method.
        base.OnPause();

        //Stop the dispatch on our NFC adapter.
        INFCImplementation.adapter?.DisableForegroundDispatch(this);
    }

    protected override void OnNewIntent(Intent intent)
    {
        //Call the base method.
        base.OnNewIntent(intent);

        //Check if this is the NFC intent.
        if (intent != null && (NfcAdapter.ActionNdefDiscovered.Equals(intent.Action) || NfcAdapter.ActionTechDiscovered.Equals(intent.Action) || NfcAdapter.ActionTagDiscovered.Equals(intent.Action)))
        {
            var test = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
            nfcImplementation.StartNFCScan(test);
        }
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
    {
        //Call the base method.
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        //Check with the permissions plugin.
        PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        //Check with the zxing plugin.
        ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

In my view model for the mainpage that is the binding context for the main page I add the following in the constructor.

    /// <summary>
    /// Constructs the scanner view model with the scanner view we want to use.
    /// </summary>
    public ScannerPageViewModel()
    {
        //Subscribe to the tag scanned event.
        CrossNFC.Current.TagScanned += ProcessNFCScanResult;
    }

    private void ProcessNFCScanResult(object sender, string e)
    {
        SetLabel(e);
    }

Ok so for the issue. I believe that this should make it so the OnNewIntent function will call the start NFC scan on the interface and then that will call the event which will fly all the way up to the view model and allow me to handle the content. I do this as I only want to scan NFC tags on one page in the app ONLY while the app is in the foreground. Everytime I get to the invoke call, the TagScanned event is null.

Placing breakpoints around I found that the following occurs when I scan a tag:

MainActivity OnPause Gets Called -> Scanner Page OnDisappearing Gets Called -> OnNewIntent Gets Called and calls the null event -> MainActivity OnResume gets called -> Scanner Page OnAppearing Gets Called

I believe that the OnDisappearing call is making the event unable to process. I based a lot of my code on the NFCForms Github project (https://github.com/poz1/NFCForms) which when downloading the sample project and running it DOES NOT trigger OnDisappearing and OnAppearing. It just calls OnPause, OnNewIntent, and OnResume and the event gets to his page.

Why is my page getting unloaded and the event doesn't get called? If I'm doing something wrong, how can I notify my ViewModel for the specific page when a tag was scanned? I'm thinking this is either an issue with the way I'm making the intent request for NFC or something not related to NFC in which I'm processing view events wrong due to the sample NFCForms application working properly on the same phone.

Edit

I made a completely new project with the same basic code and it worked the way I believe it should have. Now I'm trying to figure out why OnAppearing and OnDisappearing is called on the pages.

Edit 2

I found out that OnAppearing and OnDisappearing gets called if the pages are wrapped in a navigation page. That is why the new project which is a single view did not call it and when I added the navigation page it did call it.

HOWEVER even when changing my project to a single page, the old project I'm working on still had the event as null, while the new test project had the event as valid.

So I'm thinking that somehow I'm not doing events right?

like image 766
SolidSnake4444 Avatar asked Jan 26 '26 04:01

SolidSnake4444


1 Answers

Would this be of some use to your case ?

// In ScannerPage
protected override void OnAppearing ()
{
    base.OnAppearing ();
    MessagingCenter.Subscribe<string>(this, "eventName", (label) => {
        // do something whenever the message is sent
        Device.BeginInvokeOnMainThread (() => {
           MyScannerPageViewModel.SetLabel(label);
        });
    });
}

protected override void OnDisappearing ()
{
    base.OnDisappearing ();
    MessagingCenter.Unsubscribe<string> (this, "eventName");
}

And in the MainActivity chose where you would like to put this line

Xamarin.Forms.MessagingCenter.Send("LabelName","eventName");

EDIT: Changed the code a bit

like image 98
Greggz Avatar answered Jan 28 '26 20:01

Greggz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!