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