【问题标题】:Xamarin Android - How do I pass an event from MainActivity to ViewModel on Forms Page?Xamarin Android - 如何在表单页面上将事件从 MainActivity 传递到 ViewModel?
【发布时间】:2018-06-11 08:39:32
【问题描述】:

我有一个想要读取 NFC 标签的 Xamarin Forms 应用程序。我制作了一个名为 INFC 的接口来读取标签。

/// <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;
}

我创建了以下 Android 特定实现。

[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);
    }
}

}

我的主要活动如下。

/// <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);
    }
}

在作为主页绑定上下文的主页视图模型中,我在构造函数中添加以下内容。

    /// <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);
    }

好的,这个问题。我相信这应该可以做到,OnNewIntent 函数将调用接口上的启动 NFC 扫描,然后调用事件,该事件将一直飞到视图模型并允许我处理内容。我这样做是因为我只想在应用程序处于前台时扫描应用程序一页上的 NFC 标签。每次调用调用时,TagScanned 事件为空。

在周围放置断点我发现扫描一个标签时会出现以下情况:

调用MainActivity OnPause -> 调用扫描仪页面 OnDisappearing -> 调用 OnNewIntent 并调用 null 事件 -> 调用 MainActivity OnResume -> 调用扫描仪页面 OnAppearing

我相信 OnDisappearing 调用使事件无法处理。我的很多代码都基于 NFCForms Github 项目 (https://github.com/poz1/NFCForms),当下载示例项目并运行它时,它不会触发 OnDisappearing 和 OnAppearing。它只是调用 OnPause、OnNewIntent 和 OnResume 并且事件到达他的页面。

为什么我的页面被卸载并且事件没有被调用?如果我做错了什么,如何在扫描标签时通知特定页面的 ViewModel?我认为这要么是我对 NFC 发出意图请求的方式问题,要么是与 NFC 无关的问题,由于示例 NFCForms 应用程序在同一部手机上正常工作,我正在处理视图事件错误。

编辑

我用相同的基本代码创建了一个全新的项目,它按照我认为应该有的方式工作。现在我试图弄清楚为什么在页面上调用 OnAppearing 和 OnDisappearing。

编辑 2

我发现如果页面包含在导航页面中,则会调用 OnAppearing 和 OnDisappearing。这就是为什么作为单一视图的新项目没有调用它,而当我添加导航页面时它确实调用了它。

但是,即使将我的项目更改为单页,我正在处理的旧项目仍然将事件设为 null,而新的测试项目将事件设为有效。

所以我在想,不知怎么的,我做的事情不对?

【问题讨论】:

  • 您是否考虑过使用MessagingCenter class。另外你为什么在这里编码而不是 Pcl,它只适用于 Android 这个特定事件?
  • NFC 的读取是特定于 Android 的,因此我必须将事件结果从 Android 项目传递到我的表单 PCL。我对消息中心类不熟悉。
  • MessagingCenter 允许您将来自平台特定项目的消息发送到共享代码。我相信它可以满足您的需求
  • 由于我不知道你想在哪里触发EventViewModel 听它,我无法提供代码示例。或者至少我在你的代码中没有发现它很明显
  • 我用事件函数更新了视图模型代码。它目前所做的只是更新标签。阅读时我的印象是消息中心不如 c# 事件好,因此要避免它。事件方法在我发布的代码的全新表单项目中对我有用。我已经几个月的旧项目不会触发 Android 项目的事件,但如果我从 PCL 调用它,它会触发事件。

标签: c# android xamarin xamarin.forms xamarin.android


【解决方案1】:

这对您的情况有帮助吗?

// 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");
}

然后在MainActivity 中选择你想把这行放在哪里

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

编辑:稍微修改了代码

【讨论】:

  • 我试过这个并且遇到了同样的问题。然后我重新制作整个 Android 项目,现在我的方式和你的方式都有效。
  • 所以一开始就没有问题 :) 如果您可以投票,因为这是在 X.Forms 中使用 Events 的有效方式,我将不胜感激!
【解决方案2】:

虽然这样的回答让我非常难过,但它确实奏效了。

因为我发现制作一个新项目有效,所以我制作了一个与我的同名的新表单项目,然后从我的解决方案中删除了 Android 项目并用新项目替换它。然后我重新安装了所有 nuget 包,并将我的所有代码复制并粘贴到新项目中。

它现在可以工作了.....

所以我猜在此过程中有些东西破坏了 VS 项目的核心或其他东西。我讨厌有点挥手的答案,但这对我有用。我上面发布的所有代码都没有改变,它开始工作了。

【讨论】:

    【解决方案3】:

    上一次成功发布是 2018 年 1 月。 有人在2019年有这个工作吗?我尝试匿名 MessagingCenter 消息的每次测试都不会被订阅者听到。

    与原始海报相同的用例: 从 Android 的 MainActivty 发送消息,在共享/不可知层的 ViewModel 中监听它。

    在我的测试中,甚至在同一类或同一层中都没有听到匿名消息。

    更新: 社区的进一步输入找到了解决方案:您必须指定类型 ApplicationApplication.Currenthttps://forums.xamarin.com/discussion/comment/370364#Comment_370364

    因此,之前有效的语法在 2019 年似乎被打破了 - 有办法解决它。

    Android 层(MainActivity 或广播监听器): MessagingCenter.Send(Xamarin.Forms.Application.Current,"ISENGINEON", result.ToString()); 共享层视图模型 MessagingCenter.Subscribe&lt;Application,string&gt;(this,"ISENGINEON",OnEngineOnChanged);

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-08
      • 2022-01-21
      • 1970-01-01
      相关资源
      最近更新 更多