Skip to content

Instantly share code, notes, and snippets.

@Sylyac2000
Forked from PhongHuynh93/1 basic
Created May 22, 2018 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sylyac2000/f260cffcbbef1f26bae451d7b093959e to your computer and use it in GitHub Desktop.
Save Sylyac2000/f260cffcbbef1f26bae451d7b093959e to your computer and use it in GitHub Desktop.
livedata
ARCHITECTURE COMPONENTS - I'M NOT A PURIST BUT ...
http://hannesdorfmann.com/android/arch-components-purist
DIFFERENT LIVEDATA AND OBSERABLE
LiveData seems to be a very simplified version of RxJava’s Observable. LiveData is lifecycle aware so that we as developer don’t have to unsubscribe explicitly as we have to do with RxJava’s Observable. Both implement the Observer pattern.
LiveData doesn’t provide all this fancy functional programming operators as RxJava does although architecture components provides some functional concepts via Transformations helper class such as Transformations.map() and Transformations.switchMap().
https://developer.android.com/topic/libraries/architecture/livedata.html
Unlike a regular observable, LiveData respects the lifecycle of app components, such that the Observer can specify a Lifecycle in which it should observe.
LiveData considers an Observer to be in an active state if the Observer’s Lifecycle is in STARTED or RESUMED state.
-> fragment must extends LifecycleFragment for livedata to has 2 methods onActive() + onInactive()
public class LocationLiveData extends LiveData<Location> {
private LocationManager locationManager;
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
public LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
public class MyFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LiveData<Location> myLocationListener = ...;
Util.checkUserStatus(result -> {
if (result) {
// Notice that the observe() method passes the LifecycleOwner as the first argument.
myLocationListener.observe(this, location -> {
// update UI
});
}
});
}
}
-> extend LifecycleFragment, we will have
- If the Lifecycle is not in an active state (STARTED or RESUMED), the observer isn't called even if the value changes.
- If the Lifecycle is destroyed, the observer is removed automatically.
-> The fact that LiveData is lifecycle-aware provides us a new opportunity: we can share it between multiple activities, fragments, and so on. To keep our example simple, we can make it singleton as follows:
######################
Transformations of LiveData
Sometimes, you may want to make changes to the LiveData value before dispatching it to the observers, or you may need to return a different LiveData instance based on the value of another one.
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
// DONT DO DID
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
Android LiveData: hasObservers() vs hasActiveObservers()
https://medium.com/@ephepasha/android-livedata-hasobservers-vs-hasactiveobservers-3fce303ac813
Centralized LiveData with Kotlin 1.2
https://medium.com/@nullthemall/centralized-livedata-with-kotlin-1-2-97bff1b2cb5c
open class BaseMediatorLiveData<T> : MediatorLiveData<T>() {
lateinit var loadingLiveData: MutableLiveData<Int>
lateinit var progressLiveData: MutableLiveData<Int>
lateinit var errorLiveData: MutableLiveData<Throwable>
lateinit var progressObserver: Observer<Int>
lateinit var loadingObserver: Observer<Int>
lateinit var errorObserver: Observer<Throwable>
Using Android Architecture Components with Firebase Realtime Database (Part 1)
############################
old way
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = "MainActivity";
private final DatabaseReference ref =
FirebaseDatabase.getInstance().getReference("/hotstock");
@Override
protected void onStart() {
super.onStart();
ref.addValueEventListener(listener);
}
@Override
protected void onStop() {
ref.removeEventListener(listener);
super.onStop();
}
private ValueEventListener listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// update the UI here with values in the snapshot
String ticker = dataSnapshot.child("ticker").getValue(String.class);
tvTicker.setText(ticker);
Float price = dataSnapshot.child("price").getValue(Float.class);
tvPrice.setText(String.format(Locale.getDefault(), "%.2f", price));
}
@Override
public void onCancelled(DatabaseError databaseError) {
// handle any errors
Log.e(LOG_TAG, "Database error", databaseError.toException());
}
};
}
############################
new way
Extending LiveData with Firebase Realtime Database
public class FirebaseQueryLiveData extends LiveData<DataSnapshot> {
private static final String LOG_TAG = "FirebaseQueryLiveData";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
public FirebaseQueryLiveData(DatabaseReference ref) {
this.query = ref;
}
@Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
@Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
onActive() and onInactive(): (in the STARTED or RESUMED state), the LiveData object is "active", and the database listener will be added.
-> There's no chance of forgetting to remove the ValueEventListener on the DatabaseReference,
-> If there's a configuration change in the Activity, it will immediately observe the most recent DataSnapshot from the LiveData used in the prior Activity instance. It will not have to wait for another round trip with the server in order to start rendering that data. There's no need to use onSaveInstanceState() with LiveData.
############################
-> we need a ViewModel object to hook that up to the Activity
Because a ViewModel object survives Activity configuration changes (e.g. when the user reorients their device), its LiveData member object will be retained as well.
public class HotStockViewModel extends ViewModel {
private static final DatabaseReference HOT_STOCK_REF =
FirebaseDatabase.getInstance().getReference("/hotstock");
private final FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF);
@NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
}
But There's another subtle issue with the fact that each configuration change removes and re-adds the listener.
Each re-add of the listener effectively requires another round trip with the server to fetch the data again, and I'd rather not do that, in order to avoid consuming the user's limited mobile data. Enabling disk persistence helps, but there's a better way (stay tuned to this series for that tip!).
############################
-> used in activity
// Obtain a new or prior instance of HotStockViewModel from the
// ViewModelProviders utility class.
HotStockViewModel viewModel = ViewModelProviders.of(this).get(HotStockViewModel.class);
LiveData<DataSnapshot> liveData = viewModel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<DataSnapshot>() {
@Override
public void onChanged(@Nullable DataSnapshot dataSnapshot) {
if (dataSnapshot != null) {
// update the UI here with values in the snapshot
String ticker = dataSnapshot.child("ticker").getValue(String.class);
tvTicker.setText(ticker);
Float price = dataSnapshot.child("price").getValue(Float.class);
tvPrice.setText(String.format(Locale.getDefault(), "%.2f", price));
}
}
});
Android Architecture: Communication between ViewModel and View
Aggregating information(loading status, UI state, errors) would help keep the ViewModels lean and clean -> For me status and state
https://android.jlelse.eu/android-architecture-communication-between-viewmodel-and-view-ce14805d72bf
enum class Status {
SUCCESS,
ERROR,
NO_NETWORK,
EMPTY_FIRST_NAME,
EMPTY_LAST_NAME,
EMPTY_CITY,
INVALID_URI
}
#############
EditProfileViewModel.kt
private val status = SingleLiveEvent<Status>()
fun getStatus(): LiveData<Status> {
return status
}
fun handleImage(intent: Intent?) {
intent?.data?.let {
avatar.value = it.toString()
} ?: run { status.value = Status.INVALID_URI }
}
#############
EditProfileFragment.Kt
viewModel.getStatus().observe(this, Observer { handleStatus(it) })
private fun handleStatus(status: Status?) {
when (status) {
Status.EMPTY_FIRST_NAME -> Toast.makeText(activity, "Please enter your first name!", Toast.LENGTH_SHORT).show()
Status.EMPTY_LAST_NAME -> Toast.makeText(activity, "Please enter your last name", Toast.LENGTH_SHORT).show()
Status.EMPTY_CITY -> Toast.makeText(activity, "Please choose your home city", Toast.LENGTH_SHORT).show()
Status.INVALID_URI -> Toast.makeText(activity, "Unable to load the photo", Toast.LENGTH_SHORT).show()
Status.SUCCESS -> {
startActivity(HomeFragment.newIntent(activity))
activity.finish()
}
else -> Toast.makeText(activity, "Something went wrong, please try again!", Toast.LENGTH_SHORT).show()
}
}
#############
#############
#############
#############
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
* <p>
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
* <p>
* Note that only one observer is going to be notified of changes.
*/
https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment