Convert Android Lifecycle Callback to Reactive Observable - android

How do I convert an Android lifecycle callback to a RX Observable?
For example, onNewIntent. I could make my Activity itself a custom Observable, calling onNext for a bunch of Observers within onNewIntent - but that feels yucky. I would rather not implement a home-brew Observable if I can help it, so as to not need to mess with multi-threading etc.
namespace ...
{
using ...
/// <summary>
/// Main application activity.
/// </summary>
[Activity(...)]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IObservable<Intent>
{
private List<IObserver<Intent>> observers;
...
/// <inheritdoc/>
public IDisposable Subscribe(IObserver<Intent> observer)
{
this.observers.Add(observer);
return new Unsubscriber(this.observers, observer);
}
/// <inheritdoc/>
protected override void OnNewIntent(Intent intent)
{
foreach (var observer in this.observers)
{
observer.OnNext(intent);
}
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Intent>> observers;
private IObserver<Intent> observer;
private bool disposedValue;
public Unsubscriber(
List<IObserver<Intent>> observers,
IObserver<Intent> observer)
{
this.observers = observers;
this.observer = observer;
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
// dispose managed state (managed objects)
if (disposing &&
this.observer != null &&
this.observers.Contains(this.observer))
{
this.observers.Remove(this.observer);
}
// set large fields to null
this.observers = null;
this.observer = null;
this.disposedValue = true;
}
}
}
}
}
If there was something like a NewIntent event, this would be an easier question - there are creation operators for events, delegates, etc. But these all work with the event from outside the event - with something like an onNewIntent override implementation, I'm already inside the event (inside the monad?). So I don't know how to invert that.

I've come up with a solution. Not sure it's the most elegant, but it basically involves creating an additional event that I raise when handling the callback. Then it's just a quick trip to FromEventPattern and hey presto! Observable event data. Would be happy to hear if anyone else has any alternate approaches, but this seems to work.
namespace ...
{
using ...
/// <summary>
/// Main application activity.
/// </summary>
[Activity(...)]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IWithNewIntentObservable
{
private event EventHandler<Intent> NewIntent;
/// <inheritdoc/>
public IObservable<Intent> NewIntentObservable { get; private set; }
...
/// <inheritdoc/>
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
...
this.NewIntentObservable = Observable.FromEventPattern<Intent>(
h => this.NewIntent += h,
h => this.NewIntent -= h).Select(e => e.EventArgs);
}
/// <inheritdoc/>
protected override void OnNewIntent(Intent intent)
{
this.NewIntent?.Invoke(this, intent);
}
}
}

Related

What is the Prism version of GalaSoft.MvvmLight's AppCompatActivityBase?

I am rewriting an app that supports GalaSoft.MVVMLight in Xamarin.Android to Prism.
This Android project have this particular class GalaSoft.MVVMLight AppCompatActivityBase which inherits from AppCompatActivity.
public class AppCompatActivityBase : AppCompatActivity
{
public AppCompatActivityBase()
{
}
internal string ActivityKey
{
get;
private set;
}
/// <summary>
/// The activity that is currently in the foreground.
/// </summary>
public static AppCompatActivityBase CurrentActivity
{
get;
private set;
}
internal static string NextPageKey
{
get;
set;
}
/// <summary>
/// If possible, discards the current page and displays the previous page
/// on the navigation stack.
/// </summary>
public static void GoBack()
{
if (CurrentActivity != null)
{
CurrentActivity.OnBackPressed();
}
}
/// <summary>
/// Overrides <see cref="M:Android.App.Activity.OnResume" />. If you override
/// this method in your own Activities, make sure to call
/// base.OnResume to allow the <see cref="T:GalaSoft.MvvmLight.Views.NavigationService" />
/// to work properly.
/// </summary>
protected override void OnResume()
{
CurrentActivity = this;
if (string.IsNullOrEmpty(ActivityKey))
{
ActivityKey = NextPageKey;
NextPageKey = null;
}
base.OnResume();
}
/// <summary>
/// Overrides <see cref="M:Android.App.Activity.OnCreate" />. If you override
/// this method in your own Activities, make sure to call
/// base.OnCreate to allow the <see cref="T:GalaSoft.MvvmLight.Views.NavigationService" />
/// to work properly.
/// </summary>
protected override void OnCreate(Bundle savedInstanceState)
{
// Set CurrentActivity to the first activity that is created
if (CurrentActivity == null)
{
CurrentActivity = this;
if (string.IsNullOrEmpty(ActivityKey))
{
ActivityKey = NextPageKey;
NextPageKey = null;
}
}
base.OnCreate(savedInstanceState);
}
}
This AppCompatActivityBase is then referred to in a user preference class as shown below.
class UserPreferences : IUserPreferences
{
const int MaximumHistoryEntries = 5;
readonly ISharedPreferences preferences;
readonly ISharedPreferencesEditor editor;
public UserPreferences()
{
preferences = PreferenceManager.GetDefaultSharedPreferences(AppCompatActivityBase.CurrentActivity.ApplicationContext);
editor = preferences.Edit();
}
public event EventHandler Saved;
public string AssetsExtractedVersion
{
get
{
return preferences.GetString(AppCompatActivityBase.CurrentActivity.GetString(Resource.String.PrefKeyAssetsExtractedVersion), null);
}
set
{
editor.PutString(AppCompatActivityBase.CurrentActivity.GetString(Resource.String.PrefKeyAssetsExtractedVersion), value);
}
}
public void Save()
{
editor.Commit();
Saved?.Invoke(this, EventArgs.Empty);
}
}
Since my project now supports Prism instead of GalaSoft.MVVMLight. How can I replace AppCompatActivityBase in Prism? My background is in iOS so I'm having trouble understanding the Android end of Xamarin/Prism.
There isn't one. While Prism does have an Android specific binary to support Dependency Injection with the Xamarin.Forms Dependency Resolver which may include a Android.Content.Context, Prism itself is entirely dependent on Xamarin.Forms and runs the exact same across all platforms. As a result there is no need for a Prism specific base Activity

DependencyService.Get<T> doesn't return correct instance

I'm new to Xamarin and tried some beginner tutorials so far.
Now I wanted to build some custom stuff and need to request the current location.
For this I installed Xamarin.GooglePlayServices.Location in order to have access to the FusedLocationProviderClient class.
In MainActivity I retrieve the FusedLocationProviderClient:
public class MainActivity : FormsAppCompatActivity, ILocationProvider
{
private FusedLocationProviderClient FusedLocationClient { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
// snip
FusedLocationClient = LocationServices.GetFusedLocationProviderClient(this);
}
}
The ILocationProvider needs the method TaskCompletionSource<Model.Location> GetPosition() to be implemented:
public TaskCompletionSource<Model.Location> GetPosition()
{
// snip
if (CheckSelfPermission(Manifest.Permission_group.Location) == Permission.Granted)
{
FusedLocationClient.LastLocation.AddOnCompleteListener(new OnLocationRequestCompleteListener(this, tcs));
}
// snip
}
The UI has a button to request the current location.
Whenever the user clicks the button I get the ILocationProvider via DependencyService and execute the GetPosition method:
private void GetPositionClicked(object sender, EventArgs e)
{
var provider = DependencyService.Get<ILocationProvider>();
if(provider != null)
{
var tcs = provider.GetPosition();
// snip
}
}
The problem now is that the application crashes as soon as I try to execute the CheckSelfPermission method in GetPosition().
I set a breakpoint in GetPosition() and noticed that FusedLocationClient is null although it clearly wasn't null after OnCreate was called.
I then inspected this in OnCreate and GetPosition and noticed that they weren't the same instances which leads me to the conclusion that something is clearly wrong here.
To solve this for now I did the following:
public class MainActivity : FormsAppCompatActivity, ILocationProvider
{
internal static MainActivity Instance { get; private set; }
private FusedLocationProviderClient FusedLocationClient { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
// snip
Instance = this
// snip
}
// snip
public TaskCompletionSource<Model.Location> GetPosition()
{
if (this != Instance)
{
return Instance.GetPosition();
}
// snip
}
}
At least this works for now but I can't imagine that this is the way to go.
What I learned so far is that DependencyService seems to not get already created instances but create one instead (at least once).
What would be the correct way to call methods in MainActivity from the shared .NET Standard library?
Move those methods out of your Activity class into a standalone class (within your Xamarin.Android application (or a Xamarin.Android library project).
Assuming an interface like:
public interface ILocationProvider
{
Task<Tuple<double, double>> GetPosition();
}
Android Implementation:
public class Location_Android: Java.Lang.Object, ILocationProvider
{
private FusedLocationProviderClient FusedLocationClient { get; set; }
public Location_Android()
{
FusedLocationClient = new FusedLocationProviderClient(Application.Context);
}
public async Task<Tuple<double, double>> GetPosition()
{
var loc = await FusedLocationClient?.GetLastLocationAsync();
return new Tuple<double, double>(loc.Latitude, loc.Longitude);
}
}

Observable's Correct explanation in rxjava and rxandroid

What are the correct concepts and working of observables and observers in RxJava. I get confused by the words literal meaning. Whenever I change the values of observables its corresponding observers is not getting invoked i.e. I will explain this situation a bit more deeply, initially when I assign an observable with a list of strings(List list) and subscribe it to an observer, observer works perfectly but after that ,when I change the values of list(for example adding more String values to list) ...the observer's on next should automatically be invoked right.. but it isn't. Trying to implement in Android natively . I will be happy for some helps.
Observables work with three methods from Observer: onNext, onError and onCompleted. When you make Observable from a list and you subscribe it Observable will emit those values using onNext method and when it's finished it will call onCompleted method.
You can't change values that Observable is emitting by changing list you gave to some Observable operator. What would be you desired behaviour. Should Observable emit all elements on list change or should it emit only new changes.
This observable will emit all changes to collection made trough setCollection method:
public class CollectionObservable<T> extends Observable<T> {
private Collection<T> collection;
private List<Observer<? super T>> observers;
public CollectionObservable(Collection<T> collection) {
if (collection != null) {
this.collection = collection;
}
this.observers = new ArrayList<>(2);
}
public Collection<T> getCollection() {
return collection;
}
public void setCollection(Collection<T> collection) {
this.collection = collection;
emitValuesToAllObserver();
}
public void complete() {
if (this.collection != null) {
for (Observer<? super T> observer : this.observers) {
observer.onComplete();
}
}
}
#Override
protected void subscribeActual(Observer<? super T> observer) {
this.observers.add(observer);
emitValues(observer);
}
private void emitValuesToAllObserver() {
for (Observer<? super T> observer : this.observers) {
emitValues(observer);
}
}
private void emitValues(Observer<? super T> observer) {
if (this.collection != null) {
for (T obj : this.collection) {
observer.onNext(obj);
}
}
}
}
Note that in order to finish you manually have to call complete method.

Using Realm and LiveData. Converting LiveData<RealmResults<CustomModelObject>> to LiveData<List<CustomModelObject>>

I am trying out Realm along with Android architecture components including LiveData.
I have been following Google's Guide to Application Architecture:
https://developer.android.com/topic/libraries/architecture/guide.html
...substituting Room with Realm.
I have everything working using:
LiveData<RealmResults<CustomModelObject>>
from my repository layer right through ViewModel to View.
I am thinking it might be nicer to only have more generic types coming back from repository so LiveData<List<CustomModelObject>> rather than LiveData<RealmResults<CustomModelObject>>.
Here is a code snippet of where I have got stuck:
#NonNull
#Override
protected LiveData<List<CustomModelObject>> loadFromDb() {
return Transformations.switchMap(customModelObjectsDao.getCustomModelObjects(),
new Function<RealmResults<CustomModelObject>, LiveData<List<CustomModelObject>>>() {
#Override
public LiveData<List<CustomModelObject>> apply(RealmResults<CustomModelObject> data) {
if (data == null) {
return AbsentLiveData.create();
} else {
return customModelObjectsDao.getCustomModelObjects();
}
}
});
}
customModelObjectsDao.getCustomModelObjects() currently returns LiveData<RealmResults<Inspiration>>.
I want to transform it to LiveData<List<Inspiration>>.
I have tried various Transformations.map and Transformations.switchMap etc with no success and I think I have been staring at it too long now :)
Am I on the right path or am I missing something obvious?
Any help is greatly appreciated.
Thanks,
Paul.
UPDATE
DAO:
public RealmLiveData<CustomModelObject> getCustomModelObjects() {
return asLiveData(realm.where(CustomModelObject.class).findAllAsync());
}
asLiveData Impl:
fun <T: RealmModel> RealmResults<T>.asLiveData() = RealmLiveData<T>(this)
fun Realm.CustomModelObjectsDao(): CustomModelObjectsDao = CustomModelObjectsDao(this)
UPDATE 2
public class RealmLiveData<T> extends LiveData<RealmResults<T>> {
private RealmResults<T> results;
private final RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() {
#Override
public void onChange(RealmResults<T> results) {
setValue(results);
}
};
public RealmLiveData(RealmResults<T> realmResults) {
results = realmResults;
}
#Override
protected void onActive() {
results.addChangeListener(listener);
}
#Override
protected void onInactive() {
results.removeChangeListener(listener);
}
}
In your case, replacing LiveData<RealmResults<T> with LiveData<List<T>> would be enough to solve your problem.
However, I'd advise trying out the RealmLiveResults class that is available in the official example:
/**
* This class represents a RealmResults wrapped inside a LiveData.
*
* Realm will always keep the RealmResults up-to-date whenever a change occurs on any thread,
* and when that happens, the observer will be notified.
*
* The RealmResults will be observed until it is invalidated - meaning all local Realm instances on this thread are closed.
*
* #param <T> the type of the RealmModel
*/
public class LiveRealmResults<T extends RealmModel> extends LiveData<List<T>> {
private final RealmResults<T> results;
// The listener will notify the observers whenever a change occurs.
// The results are modified in change. This could be expanded to also return the change set in a pair.
private OrderedRealmCollectionChangeListener<RealmResults<T>> listener = new OrderedRealmCollectionChangeListener<RealmResults<T>>() {
#Override
public void onChange(#NonNull RealmResults<T> results, #Nullable OrderedCollectionChangeSet changeSet) {
LiveRealmResults.this.setValue(results);
}
};
#MainThread
public LiveRealmResults(#NonNull RealmResults<T> results) {
//noinspection ConstantConditions
if (results == null) {
throw new IllegalArgumentException("Results cannot be null!");
}
if (!results.isValid()) {
throw new IllegalArgumentException("The provided RealmResults is no longer valid, the Realm instance it belongs to is closed. It can no longer be observed for changes.");
}
this.results = results;
if (results.isLoaded()) {
// we should not notify observers when results aren't ready yet (async query).
// however, synchronous query should be set explicitly.
setValue(results);
}
}
// We should start observing and stop observing, depending on whether we have observers.
/**
* Starts observing the RealmResults, if it is still valid.
*/
#Override
protected void onActive() {
super.onActive();
if (results.isValid()) { // invalidated results can no longer be observed.
results.addChangeListener(listener);
}
}
/**
* Stops observing the RealmResults.
*/
#Override
protected void onInactive() {
super.onInactive();
if (results.isValid()) {
results.removeChangeListener(listener);
}
}
}
This way your dao can expose LiveData<List<T>>, and your Transformations.map() should work.
If you need:
val list : LiveData<List<mRealmObject>>
First: Create this file:
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) :
LiveData<RealmResults<T>>() {
private val listener: RealmChangeListener<RealmResults<T>> =
RealmChangeListener { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}
fun <T: RealmModel> RealmResults<T>.asLiveData() = RealmLiveData<T>(this)
Second: Get your new RealmLiveData :
val mRealmLiveData = realm.where(mRealmObject::class.java).findAllAsync().asLiveData()
And Finally, get the list you need like this:
val list: LiveData<List<mRealmObject>> = Transformations.map(mRealmLiveData) {
realmResult ->
realm.copyFromRealm(realmResult)
}
If you use it in a ViewModel:
//get realm instance
val realm: Realm by lazy {
Realm.getDefaultInstance()
}
// get your live data
val list: LiveData<List<mRealmObject>> = Transformations.map(mRealmLiveData) {
realmResult ->
realm.copyFromRealm(realmResult)
}
// Close your realm instance onCleraded
override fun onCleared() {
realm.close()
super.onCleared()
}

Android equivalent to NSNotificationCenter

In the process of porting an iPhone application over to android, I am looking for the best way to communicate within the app. Intents seem to be the way to go, is this the best (only) option? NSUserDefaults seems much lighter weight than Intents do in both performance and coding.
I should also add I have an application subclass for state, but I need to make another activity aware of an event.
The best equivalent I found is LocalBroadcastManager which is part of the Android Support Package.
From the LocalBroadcastManager documentation:
Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.
When using this, you can say that an Intent is an equivalent to an NSNotification. Here is an example:
ReceiverActivity.java
An activity that watches for notifications for the event named "custom-event-name".
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Register to receive messages.
// This is just like [[NSNotificationCenter defaultCenter] addObserver:...]
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}
// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
// This is somewhat like [[NSNotificationCenter defaultCenter] removeObserver:name:object:]
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}
SenderActivity.java
The second activity that sends/broadcasts notifications.
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Every time a button is clicked, we want to broadcast a notification.
findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();
}
});
}
// Send an Intent with an action named "custom-event-name". The Intent sent should
// be received by the ReceiverActivity.
private void sendMessage() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my message!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
With the code above, every time the button R.id.button_send is clicked, an Intent is broadcasted and is received by mMessageReceiver in ReceiverActivity.
The debug output should look like this:
01-16 10:35:42.413: D/sender(356): Broadcasting message
01-16 10:35:42.421: D/receiver(356): Got message: This is my message!
Here is something similar to #Shiki answer, but from the angle of iOS developers and Notification center.
First create some kind of NotificationCenter service:
public class NotificationCenter {
public static void addObserver(Context context, NotificationType notification, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).registerReceiver(responseHandler, new IntentFilter(notification.name()));
}
public static void removeObserver(Context context, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(responseHandler);
}
public static void postNotification(Context context, NotificationType notification, HashMap<String, String> params) {
Intent intent = new Intent(notification.name());
// insert parameters if needed
for(Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
intent.putExtra(key, value);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
Then, you will also need some enum type to be secure of mistakes in coding with strings - (NotificationType):
public enum NotificationType {
LoginResponse;
// Others
}
Here is usage(add/remove observers) for example in activities:
public class LoginActivity extends AppCompatActivity{
private BroadcastReceiver loginResponseReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do what you need to do with parameters that you sent with notification
//here is example how to get parameter "isSuccess" that is sent with notification
Boolean result = Boolean.valueOf(intent.getStringExtra("isSuccess"));
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//subscribe to notifications listener in onCreate of activity
NotificationCenter.addObserver(this, NotificationType.LoginResponse, loginResponseReceiver);
}
#Override
protected void onDestroy() {
// Don't forget to unsubscribe from notifications listener
NotificationCenter.removeObserver(this, loginResponseReceiver);
super.onDestroy();
}
}
and here is finally how we post notification to NotificationCenter from some callback or rest service or whatever:
public void loginService(final Context context, String username, String password) {
//do some async work, or rest call etc.
//...
//on response, when we want to trigger and send notification that our job is finished
HashMap<String,String> params = new HashMap<String, String>();
params.put("isSuccess", String.valueOf(false));
NotificationCenter.postNotification(context, NotificationType.LoginResponse, params);
}
that's it, cheers!
You could try this: http://developer.android.com/reference/java/util/Observer.html
I found that the usage of EventBus of Guava lib is the simplest way for publish-subscribe-style communication between components without requiring the components to explicitly register with one another
see their sample on https://code.google.com/p/guava-libraries/wiki/EventBusExplained
// Class is typically registered by the container.
class EventBusChangeRecorder {
#Subscribe public void recordCustomerChange(ChangeEvent e) {
recordChange(e.getChange());
}
// somewhere during initialization
eventBus.register(this);
}
// much later
public void changeCustomer() {
eventBus.post(new ChangeEvent("bla bla") );
}
you can add this lib simply on Android Studio by adding a dependency to your build.gradle:
compile 'com.google.guava:guava:17.0'
You could use this: http://developer.android.com/reference/android/content/BroadcastReceiver.html, which gives a similar behavior.
You can register receivers programmatically through Context.registerReceiver(BroadcastReceiver, IntentFilter) and it will capture intents sent through Context.sendBroadcast(Intent).
Note, though, that a receiver will not get notifications if its activity (context) has been paused.
Kotlin: Here's a #Shiki's version in Kotlin with a little bit refactor in a fragment.
Register the observer in Fragment.
Fragment.kt
class MyFragment : Fragment() {
private var mContext: Context? = null
private val mMessageReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//Do something here after you get the notification
myViewModel.reloadData()
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onStart() {
super.onStart()
registerSomeUpdate()
}
override fun onDestroy() {
LocalBroadcastManager.getInstance(mContext!!).unregisterReceiver(mMessageReceiver)
super.onDestroy()
}
private fun registerSomeUpdate() {
LocalBroadcastManager.getInstance(mContext!!).registerReceiver(mMessageReceiver, IntentFilter(Constant.NOTIFICATION_SOMETHING_HAPPEN))
}
}
Post notification anywhere. Only you need the context.
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(Constant.NOTIFICATION_SOMETHING_HAPPEN))```
PS:
you can add a Constant.kt like me for well organize the notifications.
Constant.kt
object Constant {
const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
For the context in a fragment, you can use activity (sometimes null) or conext like what I used.
I wrote a wrapper that can do this same job, equivalent to iOS using LiveData
Wrapper:
class ObserverNotify {
private val liveData = MutableLiveData<Nothing>()
fun postNotification() {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = liveData.value
}
}
}
fun observeForever(observer: () -> Unit) {
liveData.observeForever { observer() }
}
fun observe(owner: LifecycleOwner, observer: () -> Unit) {
liveData.observe(owner) { observer()}
}
}
class ObserverNotifyWithData<T> {
private val liveData = MutableLiveData<T>()
fun postNotification(data: T) {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = data
}
}
}
fun observeForever(observer: (T) -> Unit) {
liveData.observeForever { observer(it) }
}
fun observe(owner: LifecycleOwner, observer: (T) -> Unit) {
liveData.observe(owner) { observer(it) }
}
}
Declaring observer types:
object ObserverCenter {
val moveMusicToBeTheNextOne: ObserverNotifyWithData<Music> by lazy { ObserverNotifyWithData() }
val playNextMusic: ObserverNotify by lazy { ObserverNotify() }
val newFCMTokenDidHandle: ObserverNotifyWithData<String?> by lazy { ObserverNotifyWithData() }
}
In the activity to observe:
ObserverCenter.newFCMTokenDidHandle.observe(this) {
// Do stuff
}
To notify:
ObserverCenter.playNextMusic.postNotification()
ObserverCenter.newFCMTokenDidHandle.postNotification("MyData")
Answer of #Shiki could be right in June 2020, but in January 2022, LocalBroadcastManager happened to be deprecated.
After two days of research, I ended up finding that SharedFlow was indicated by Android to "send ticks to the rest of the app so that all the content refreshes periodically at the same time".
Meaning, more or less, what we could expect from the NSNotificationCenter of Swift.
And here is the way I implemented the Shared Flow in my app:
First, you need to create an InAppNotif Singleton, which is actually a shared ViewModel for your activity (be caution to this last point: shared for your activity, not your all app^^)
enum class InAppNotifName {
NotifNameNumber1,
NotifNameNumber2,
NotifNameNumber3
}
object InAppNotif: ViewModel() {
private val _sharedNotif = MutableSharedFlow<InAppNotifName>(0)
val sharedNotif: SharedFlow<InAppNotifName> = _sharedNotif.asSharedFlow()
private fun sendNotif(name: InAppNotifName) {
CoroutineScope(Default).launch {
_sharedNotif.emit(name)
}
}
public fun notifyNotif1() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif2() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif3() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
}
Second Step, only required if you have many Fragments receiving in app notifications, and you don't want to repeat yourself, would be to create an "Receiving Notif" interface
fun AnyReceivingNotif.observeInAppNotif() {
CoroutineScope(Default).launch {
InAppNotif.sharedNotif.collect {
onReceivingInAppNotif(it)
}
}
}
interface AnyReceivingNotif {
suspend fun onReceivingInAppNotif(value: InAppNotifName)
}
By the way, the "suspend" word is useful only if you need to update the UI upon receiving the notification.
Finally, from any object which is to receive InAppNotif, all you would need to do is get it be conform to your AnyReceivingNotif interface, and then complete the onReceivingInAppNotif function
class MyFragment: Fragment(), AnyReceivingNotif {
override suspend fun onReceivingInAppNotif(value: InAppNotifName) {
when (value) {
InAppNotifName.NotifNameNumber1 -> { /* Do complicated things */ }
InAppNotifName.NotifNameNumber2 -> { /* Do some stuff */ }
InAppNotifName.NotifNameNumber3 -> {
withContext(Default){
/* Update the UI */
}
}
}
}
}
You could use weak references.
This way you could manage the memory yourself and add and remove observers as you please.
When you addObserver add these parameters - cast that context from the activity you are adding it in to the empty interface, add a notification name, and call the method to run interface.
The method to run interface would have a function that is called run to return the data that you are passing something like this
public static interface Themethodtorun {
void run(String notification_name, Object additional_data);
}
Create a observation class that invokes a reference with a empty interface.
Also construct your Themethodtorun interface from the context being passed in the addobserver.
Add the observation to a data structure.
To call it would be the same method however all you need to do is find the specific notification name in the data structure, use the Themethodtorun.run(notification_name, data).
This will send a callback to where ever you created an observer with a specific notification name.
Dont forget to remove them when your done!
This is good reference for weak references.
http://learningviacode.blogspot.co.nz/2014/02/weak-references-in-java.html
I am in the process of uploading this code to github. Keep eyes open!

Categories

Resources