-
-
Save Sylyac2000/f260cffcbbef1f26bae451d7b093959e to your computer and use it in GitHub Desktop.
livedata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
############# | |
############# | |
############# | |
############# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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