How to update UI of an android activity from a broadcast receiver.

Phil 166 Reputation points
2021-03-23T14:35:36.117+00:00

Hi there

I am using androidX with xamarin. I am currently experimenting with background workers for loading data asynchronously. I am trying to learn the different concepts and patterns that glue the android apps together.

I have some basic test code with a text field and a button. when I press the button it fires a WorkerRequest. The worker request then currently just has a thread.delay for 5 seconds.

I'm thinking in the real world you might want to download a large file, and so use a method like this to do the downloading on a background thread.
I have looked at some tutorials on msdn and android and I can't work out how registering a receiver on an activity works when it comes to using it.

Here is an example of my code:

Main Activity

 Button _eventBtn;
        TextView _textView;
        UiUpdateReceiver _broadcastReceiver;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            _eventBtn = FindViewById<Button>(Resource.Id.countingTextButton);
            _eventBtn.Click += EventBtn_Click;

            _textView = FindViewById<TextView>(Resource.Id.countingText);


            _broadcastReceiver = new UiUpdateReceiver();
            RegisterReceiver(_broadcastReceiver, new IntentFilter());

            ObserveWorkers();

        }

        protected override void OnDestroy()
        {
            UnregisterReceiver(_broadcastReceiver);
            base.OnDestroy();
        }

        private void EventBtn_Click(object sender, System.EventArgs e)
        {
            _eventBtn.Enabled = false;

            //Intent startBackgroundWorker = new Intent(this, typeof(BackgroundWorkerReceiver));
            //SendBroadcast(startBackgroundWorker);

            var simpleListenableWorkerRequest =
                    new OneTimeWorkRequest.Builder(typeof(BackgroundWorker))
                    .AddTag(BackgroundWorker.TAG)
                    .Build();

            WorkManager.GetInstance(this).BeginUniqueWork(
                BackgroundWorker.TAG, ExistingWorkPolicy.Keep, simpleListenableWorkerRequest)
                .Enqueue();

            _eventBtn.Enabled = true;
        }

        protected void ObserveWorkers()
        {
            var workManager = WorkManager.GetInstance(this);

            var simpleListenableWorkerObserver = workManager.GetWorkInfosByTagLiveData(BackgroundWorker.TAG);
            simpleListenableWorkerObserver.Observe(this, this);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

        public void OnChanged(Object p0)
        {
            var workInfos = p0.JavaCast<JavaList<WorkInfo>>();
            StringBuilder textViewText = default;

            RunOnUiThread(() =>
            {
                textViewText = new StringBuilder(_textView.Text);
            });

            foreach (var workInfo in workInfos)
            {
                //Ignore the default Xamarin Tag when getting the Tag.
                var name = workInfo.Tags.First(t => !t.Contains("."));
                var progress = workInfo.Progress?.GetInt("Progress", -1) ?? -1;
                if (progress == -1)
                {
                    textViewText.Append($"{System.Environment.NewLine}{name}:{workInfo.GetState()}");
                }
                else
                {
                    textViewText.Append($"{System.Environment.NewLine}{name}:{workInfo.GetState()} {progress}%");
                }
            }



            RunOnUiThread(() =>
            {
                _textView.Text = textViewText.ToString();
            });

        }

BackgroundWorker

  public const string TAG = "BackgroundWorker";

        Context localContext;
        WorkerParameters localWorkerParameters;

        public BackgroundWorker(Context context, WorkerParameters workerParameters) :
           base(context, workerParameters)
        {
            localContext = context;
            localWorkerParameters = workerParameters;
        }

        public override IListenableFuture StartWork()
        {
            Log.Debug(TAG, "Started.");
            return CallbackToFutureAdapter.GetFuture(this);
        }


        public Java.Lang.Object AttachCompleter(CallbackToFutureAdapter.Completer p0)
        {

            Log.Debug(TAG, $"Executing.");

            //Switch to background thread.
            Task.Run(async () =>
            {
                var delaySeconds = 5;
                var progress = 0;
                var progressIncrement = 100 / delaySeconds;
                var dataBuilder = new Data.Builder();

                for (int i = 0; i < delaySeconds + 1; i++)
                {
                    await Task.Delay(1000);
                    progress += progressIncrement;
                    dataBuilder.PutInt("Progress", progress);
                    SetProgressAsync(dataBuilder.Build());
                }

                Log.Debug(TAG, "Completed.");

                //Set a Success Result on the completer and return it.
                return p0.Set(Result.InvokeSuccess());
            });

            return TAG;
        }

BroadcastReceiver

public override void OnReceive(Context context, Intent intent)
        {
            Intent updateUiValueIntent = new Intent(context, typeof(MainActivity));
            updateUiValueIntent.PutExtra("testValue", 10);




        }

My idea was:
Click the button to fire off the worker service (maybe use a broadcast for this also).
When the worker has finished fire a broadcast for the MainActivity.
Update the UI with the broadcast.

In my main activity I have registered the receiver in the create method with
** _broadcastReceiver = new UiUpdateReceiver();
RegisterReceiver(_broadcastReceiver, new IntentFilter());**

(I also know I need to UnregisterReceiver in the Destory)

This is where the examples and documentation seems to stop. I'm not sure what Registering it actually does? (I am obviously missing a piece of the puzzle).

I am kind of expecting to maybe need a method that would be something like:

 private async void UpdateUiFrombroadcast()
        {
            Intent incomingIntent = new Intent(this, typeof(UiUpdateReceiver));
            int newValue = incomingIntent.GetIntExtra("values", 0);


            //Update ui with value?
        }

I can't find syntax to show me exactly what this would be. Also doing something like this implies that I could pass the intent without a receiver?
I can't see any obvious event on the receiver when registered to call when something has made a broadcast.

Maybe I am going about this wrong and doing some kind of anti-pattern for what I am doing?

What I am doing to be clear is:
Start Activity
Click button on activity
Kicks off a background Worker
When the background worker is finished, if the activity is still open/in the foreground update UI.

Any help with the correct concept of how to glue this together would be appreciated.

Many thanks

Developer technologies | .NET | Xamarin
0 comments No comments
{count} votes

Accepted answer
  1. JessieZhang-MSFT 7,716 Reputation points Microsoft External Staff
    2021-03-24T08:12:29.54+00:00

    Hello,

    Welcome to our Microsoft Q&A platform!

    How to update UI of an android activity from a broadcast receiver.

    There are several ways to do this.

    A method is to define a static reference of your activity inside your activity, and call the update function in your broadcast receiver.

    You can refer the following code:

    public class MainActivity : AppCompatActivity  
    {  
       //define an instance of current activity  
    
        public static MainActivity Instance;  
    
        protected override void OnCreate(Bundle savedInstanceState)  
        {  
            base.OnCreate(savedInstanceState);  
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);  
            // Set our view from the "main" layout resource  
            SetContentView(Resource.Layout.activity_main);  
    
            Instance = this;  
    
        }  
       // define a method in your activity  
        public void updateUI(string value) {   
        // here you can update the UI  
          
          
         }  
       }  
    

    And in your BroadcastReceiver, do like this:

    [BroadcastReceiver(Enabled = true, Exported = true, Permission = "android.permission.BROADCAST_SMS")]  
    [IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED", "android.provider.Telephony.SMS_DELIVER" })]  
    public class MyReceiver: BroadcastReceiver  
    {  
        public override void OnReceive(Context context, Intent intent)  
        {  
            string result = " test";  
    
            MainActivity.Instance.updateUI(result);  
    
        }  
     }  
    

    Another method is to use messagecenter to achieve this.

    You can publish a message in your BroadcastReceiver, for example:

       MessagingCenter.Send<MainPage, string>(this, "Hi", "John");  
    

    And subscribe to a message in ypur activity:

       MessagingCenter.Subscribe<MainPage, string>(this, "Hi", async (sender, arg) =>  
      {  
        await DisplayAlert("Message received", "arg=" + arg, "OK");  
      });  
    

    For more details, you can check: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center

    Best Regards,

    Jessie Zhang

    ---
    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.