I have a need to transform one type of data, returned by a LiveData object, into another form on a background thread to prevent UI lag.
In my specific case, I have:
MyDBRow objects (POJOs consisting of primitive longs and Strings);
a Room DAO instance emitting these via a LiveData<List<MyDBRow>>; and
a UI expecting richer MyRichObject objects (POJOs with the primitives inflated into e.g. date/time objects)
so I need to transform my LiveData<List<MyDBRow>> into a LiveData<List<MyRichObject>>, but not on the UI thread.
The Transformations.map(LiveData<X>, Function<X, Y>) method does this needed transformation, but I can't use this because it executes the transformation on the main thread:
Applies the given function on the main thread to each value emitted by source LiveData and returns LiveData, which emits resulting values.
The given function func will be executed on the main thread.
What is a clean way to make LiveData transformations occur:
somewhere off the main thread, and
only as needed (i.e. only when something is observing the intended transformation)?
The original, “source” LiveData can be monitored by a new Observer instance.
This Observer instance, when source LiveData is emitted, can prepare a background thread to perform the needed transformation and then emit it via a new, “transformed” LiveData.
The transformed LiveData can attach the aforementioned Observer to the source LiveData when it has active Observers, and detach them when it doesn't, ensuring that the source LiveData is only being observed when necessary.
The question gives an example source LiveData<List<MyDBRow>> and needs a transformed LiveData<List<MyRichObject>>. A combined transformed LiveData and Observer could look something like this:
class MyRichObjectLiveData
extends LiveData<List<MyRichObject>>
implements Observer<List<MyDBRow>>
{
#NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(#NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
// only watch the source LiveData when something is observing this
// transformed LiveData
#Override protected void onActive() { sourceLiveData.observeForever(this); }
#Override protected void onInactive() { sourceLiveData.removeObserver(this); }
// receive source LiveData emission
#Override public void onChanged(#Nullable List<MyDBRow> dbRows) {
// set up a background thread to complete the transformation
AsyncTask.execute(new Runnable() {
#Override public void run() {
assert dbRows != null;
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
// use LiveData method postValue (rather than setValue) on
// background threads
postValue(myRichObjects);
}
});
}
}
If multiple such transformations are needed, the above logic could be made generic like this:
abstract class TransformedLiveData<Source, Transformed>
extends LiveData<Transformed>
implements Observer<Source>
{
#Override protected void onActive() { getSource().observeForever(this); }
#Override protected void onInactive() { getSource().removeObserver(this); }
#Override public void onChanged(#Nullable Source source) {
AsyncTask.execute(new Runnable() {
#Override public void run() {
postValue(getTransformed(source));
}
});
}
protected abstract LiveData<Source> getSource();
protected abstract Transformed getTransformed(Source source);
}
and the subclass for the example given by the question could look something like this:
class MyRichObjectLiveData
extends TransformedLiveData<List<MyDBRow>, List<MyRichObject>>
{
#NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(#NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
#Override protected LiveData<List<MyDBRow>> getSource() {
return sourceLiveData;
}
#Override protected List<MyRichObject> getTransformed(List<MyDBRow> myDBRows) {
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
return myRichObjects;
}
}
It may be eaiser to do using MediatorLiveData. Transformations.map() is implemented with MediatorLiveData under the hood.
#MainThread
public static <X, Y> LiveData<Y> mapAsync(
#NonNull LiveData<X> source,
#NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable final X x) {
AsyncTask.execute(new Runnable() {
#Override
public void run() {
result.postValue(mapFunction.apply(x));
}
});
}
});
return result;
}
Listen to a MediatorLiveData<T> that listens to two other LiveData<T>s.
For example:
val exposed: LiveData<List<T>> = MediatorLiveData<List<T>>().apply {
addSource(aLiveDataToMap) { doWorkOnAnotherThread(it) }
addSource(aMutableLiveData) { value = it }
}
private fun doWorkOnAnotherThread(t: T) {
runWorkOnAnotherThread {
val t2 = /* ... */
aMutableLiveData.postValue(t2)
}
}
Whenever aLiveDataToMap changes, it will trigger doWorkOnAnotherThread() which will then set the value of aMutableLiveData, which finally sets to value of exposed, which a lifecycle-owner will be listening to. Replace Ts with your desired type.
Thanks to #jaychang0917
Kotlin Form :
#MainThread
fun <X, Y> mapAsync(source: LiveData<X>, mapFunction: androidx.arch.core.util.Function<X, Y>): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source) { x -> AsyncTask.execute { result.postValue(mapFunction.apply(x)) } }
return result
}
A solution with coroutines:
class RichLiveData(val rows: LiveData<List<MyDBRow>>) : LiveData<List<MyRichObject>>(),
CoroutineScope by CoroutineScope(Dispatchers.Default) {
private val observer = Observer<List<MyDBRow>> { rows ->
launch {
postValue(/*computationally expensive stuff which returns a List<MyRichObject>*/)
}
}
override fun onActive() {
rows.observeForever(observer)
}
override fun onInactive() {
rows.removeObserver(observer)
}
}
Another possible solution with coroutines:
object BackgroundTransformations {
fun <X, Y> map(
source: LiveData<X>,
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, Observer<X> { x ->
if (x == null) return#Observer
CoroutineScope(Dispatchers.Default).launch {
result.postValue(mapFunction(x))
}
})
return result
}
fun <X, Y> switchMap(
source: LiveData<X>,
switchMapFunction: (X) -> LiveData<Y>
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, object : Observer<X> {
var mSource: LiveData<Y>? = null
override fun onChanged(x: X) {
if (x == null) return
CoroutineScope(Dispatchers.Default).launch {
val newLiveData = switchMapFunction(x)
if (mSource == newLiveData) {
return#launch
}
if (mSource != null) {
result.removeSource(mSource!!)
}
mSource = newLiveData
if (mSource != null) {
result.addSource(mSource!!) { y ->
result.setValue(y)
}
}
}
}
})
return result
}
}
Hope it helps
How about like this:
#Query("SELECT * FROM " + PeriodicElement.TABLE_NAME)
abstract fun getAll(): LiveData<List<PeriodicElement>>
fun getAllElements(): LiveData<HashMap<String, PeriodicElement>> {
return Transformations.switchMap(getAll(), ::transform)
}
private fun transform(list: List<PeriodicElement>): LiveData<HashMap<String, PeriodicElement>> {
val map = HashMap<String, PeriodicElement>()
val liveData = MutableLiveData(map)
AsyncTask.execute {
for (p in list) {
map[p.symbol] = p
if (!liveData.hasObservers()) {
//prevent memory leak
break
}
}
liveData.postValue(map)
}
return liveData
}
Related
In my app, I have a ViewModel looks like that:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
}
Also the code of MyRepository#getById (myDao is a room DAO and it is injected):
public LiveData<MyEntity> getById(final Long id) {
return myDao.getById(id);
}
The code of MyDao#getById:
#Query(
"SELECT * FROM myTable WHERE id=:id"
)
LiveData<MyEntity> getById(final Long id);
I also try to test this ViewModel using
myExampleViewModel.init(myId);
myExampleViewModel.toggleStar();
but after the init call my LiveData value is always null.
My first question is: is it a best practice to use getValue() on my LiveData or should I use Transformation.map?
My second question is: in my test, how can I have a LiveData populated? I tried to use CountingTaskExecutorRule and InstantTaskExecutorRule but without any success.
Thank you for your help!
I understood why myLiveData is not populated in my test. According to the documentation "LiveData objects that are lazily calculated on demand." and LiveData#getValue only get the value if the LiveData is already populated but doesn't calculate the value.
So I fixed my test adding a getter on my LiveData and an observer on my LiveData to force the calculation like that LiveDataUtil.getValue(myExampleViewModel.getMyLiveData()); with LiveDataUtil#getValue:
public class LiveDataUtil {
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
#Override
public void onChanged(#Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
new Handler(Looper.getMainLooper()).post(() -> liveData.observeForever(observer));
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
After this fix, MyExampleViewModel class looks like:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
public LiveData<MyEntity> getMyLiveData() {
return myLiveData;
}
}
And my test method:
myExampleViewModel.init(myId);
LiveDataUtil.getValue(myExampleViewModel.getMyLiveData());
myExampleViewModel.toggleStar();
I fixed my test but I still don't know if using LiveData.getValue is a best practice and I found few documentation on this topic. So, I'm interested in this topic if you have more information.
I'm trying to implement MVVM pattern usingGoogle's android architecture components while using RX Java in NetworkBoundResource. However I'm having a difficult time finding a way to communicate the error response from network call to activity.
here is a link to the original github project.
I have read this post about "Refactoring google's NetworkBoundResource class to use RxJava instead of LiveData" but still not clear how to actually solve the problem.
would appreciate if you could direct me to a code based solution for this scenario for better understanding.
cheers!
GithubRepository:
#Singleton
public class GithubRepository {
private GithubDao githubDao;
private GithubApiService githubApiService;
public GithubRepository(GithubDao githubDao, GithubApiService githubApiService) {
this.githubDao = githubDao;
this.githubApiService = githubApiService;
}
public Observable<Resource<List<GithubEntity>>> getRepositories(Long page) {
return new NetworkBoundResource<List<GithubEntity>, GithubApiResponse>() {
#Override
protected void saveCallResult(#NonNull GithubApiResponse item) {
List<GithubEntity> repositories = item.getItems();
for (GithubEntity githubEntity : repositories) {
githubEntity.setPage(page);
githubEntity.setTotalPages(item.getTotalCount());
}
githubDao.insertRepositories(repositories);
}
#Override
protected boolean shouldFetch() {
return true;
}
#NonNull
#Override
protected Flowable<List<GithubEntity>> loadFromDb() {
List<GithubEntity> repositories = githubDao.getRepositoriesByPage(page);
return (repositories == null || repositories.isEmpty()) ?
Flowable.empty() : Flowable.just(repositories);
}
#NonNull
#Override
protected Observable<Resource<GithubApiResponse>> createCall() {
return githubApiService.fetchRepositories(QUERY_SORT, QUERY_ORDER, page)
.flatMap(response ->
Observable.just(response.isSuccessful()
? Resource.success(response.body())
: Resource.error("", new GithubApiResponse())));
}
}.getAsObservable();
}
}
.:UPDATE:.
So I can see that the the in NetworkBoundResource the doOnError and onErrorResumeNext are handling the error response. but i don't know how to get this response in the activity. I am having a hard time understanding it.
NetworkBoundResource:
public abstract class NetworkBoundResource<ResultType, RequestType> {
private Observable<Resource<ResultType>> result;
#MainThread
protected NetworkBoundResource() {
Observable<Resource<ResultType>> source;
if (shouldFetch()) {
source = createCall()
.subscribeOn(Schedulers.io())
.doOnNext(apiResponse -> saveCallResult(processResponse(apiResponse)))
.flatMap(apiResponse -> loadFromDb().toObservable().map(Resource::success))
.doOnError(t -> onFetchFailed())
.onErrorResumeNext(t -> {
return loadFromDb()
.toObservable()
.map(data -> Resource.error(t.getMessage(), data));
})
.observeOn(AndroidSchedulers.mainThread());
} else {
source = loadFromDb()
.toObservable()
.map(Resource::success);
}
result = Observable.concat(
loadFromDb()
.toObservable()
.map(Resource::loading)
.take(1),
source
);
}
public Observable<Resource<ResultType>> getAsObservable() {return result;}
protected void onFetchFailed() {}
#WorkerThread
protected RequestType processResponse(Resource<RequestType> response) {return response.data;}
#WorkerThread
protected abstract void saveCallResult(#NonNull RequestType item);
#MainThread
protected abstract boolean shouldFetch();
#NonNull
#MainThread
protected abstract Flowable<ResultType> loadFromDb();
#NonNull
#MainThread
protected abstract Observable<Resource<RequestType>> createCall();
}
GithubListViewModel:
public class GithubListViewModel extends ViewModel {
private Long currentPage = 0l;
private GithubRepository repository;
private List<GithubEntity> repositories = new ArrayList<>();
private SingleLiveEvent<List<GithubEntity>> repoListLiveData = new SingleLiveEvent<>();
#Inject
public GithubListViewModel(GithubDao githubDao, GithubApiService githubApiService) {
repository = new GithubRepository(githubDao, githubApiService);
}
public void fetchRepositories() {
repository.getRepositories(++currentPage)
.subscribe(resource -> {
if(resource.isLoaded()) {
repositories.addAll(resource.data);
getRepositoryListLiveData().postValue(resource.data);
}
});
}
public List<GithubEntity> getRepositories() {
return repositories;
}
public SingleLiveEvent<List<GithubEntity>> getRepositoryListLiveData() {
return repoListLiveData;
}
public boolean isLastPage() {
return getRepositoryListLiveData().getValue() != null &&
!getRepositoryListLiveData().getValue().isEmpty() ?
getRepositoryListLiveData().getValue().get(0).isLastPage() :
false;
}
}
GithubActivity:
public class GithubListActivity extends AppCompatActivity implements RecyclerLayoutClickListener {
...
private void initialiseViewModel() {
githubListViewModel = ViewModelProviders.of(this, viewModelFactory).get(GithubListViewModel.class);
githubListViewModel.getRepositoryListLiveData().observe(this, repositories -> {
if(githubListAdapter.getItemCount() == 0) {
if(!repositories.isEmpty()) {
animateView(repositories);
} else displayEmptyView();
} else if(!repositories.isEmpty()) displayDataView(repositories);
});
}
...
githubListViewModel.fetchRepositories();
...
}
I am working on livedata. I have two apis but one api is dependent on another api. Based on first api response i am calling another api using livedata observer. I am calling from inside observer is this a right approach or any other alternative
mainViewModel.getListLiveData().observe(MainActivity.this, new Observer<List<Student>>() {
#Override
public void onChanged(#Nullable List<Student> list) {
if(list.size() > 0){
mainViewModel.getStudentLiveData().observe(MainActivity.this, new Observer<Student>() {
#Override
public void onChanged(#Nullable Student student) {
}
});
}
}
});
Observe on the student LiveData exposed by the mainViewModel. In the viewmodel make student live data change with change in List LiveData using Transformations or using a MediatorLiveData
In your activity:
mainViewModel.getStudentLiveData().observe(this, new Observer<Student>() {
student -> {}
});
In your viewmodel:
private MutableLiveData<List< Student>> studentListLiveData = new MutableLiveData(); // this will hold result of your first api call
private MutableLiveData<Student> studentLiveData = new MutableLiveData(); // this will hold result of your second api call
private void fetchData() {
fetchStudentList(new Callback {
result -> {
studentListLiveData.value = result;
fetchStudent(new Callback {
result -> { studentLiveData.value = result; }
});
}
})
}
public LiveData<Student> getStudentLiveData() {
return studentLiveData;
}
I'm working on a project in android for a udacity course I'm currently trying to implement a search function while adhering to android architecture components and using firestore and room I'm fairly new to all these concepts so please point out anything that seems wrong.
So I made a database repository to keep my firestore and room databases in sync and to deliver the data. I'm then using viewmodel and the observer pattern (I think) so my observer gets the data and looks for changes gives it to my adapter (refreshMyList(List)) which populates a recyclerview like this :
contactViewModel = ViewModelProviders.of(this).get(ContactsViewModel.class);
contactViewModel.getAllContacts().observe(this, new
Observer<List<DatabaseContacts>>() {
#Override
public void onChanged(#Nullable List<DatabaseContacts>
databaseContacts) {
ArrayList<DatabaseContacts> tempList = new ArrayList<>();
tempList.addAll(databaseContacts);
contactsAdapter.refreshMyList(tempList);
if (tempList.size() < 1) {
results.setVisibility(View.VISIBLE);
} else {
results.setVisibility(View.GONE);
}
}
});
I now want to perform a search of the data, I have my room queries all set up fine and I have methods in my data repository to get contacts based on a search string but I cant seem to refresh my list I've read that there are ways to do it like Transformations.switchMap ? but i cant seem to wrap my head around how it works can anyone help me
Currently I'm trying to return a List of results from an async task, it used to return live data but I changed it as getValue() was always null, not sure if that's correct, heres the async :
private static class searchContactByName extends AsyncTask<String, Void,
ArrayList<DatabaseContacts>> {
private LiveDatabaseContactsDao mDao;
searchContactByName(LiveDatabaseContactsDao dao){
this.mDao = dao;
}
#Override
protected ArrayList<DatabaseContacts> doInBackground(String... params) {
ArrayList<DatabaseContacts> contactsArrayList = new ArrayList<>();
mDao.findByName("%" + params[0] + "%");
return contactsArrayList;
}
}
I call this from my contacts repository in its own sort of wrapper :
public List<DatabaseContacts> getContactByName(String name) throws
ExecutionException, InterruptedException {
//return databaseContactsDao.findByName(name);
return new searchContactByName(databaseContactsDao).execute(name).get();
}
and this is called from my view model like this :
public List<DatabaseContacts> getContactByName(String name) throws
ExecutionException, InterruptedException {
return contactRepository.getContactByName(name);
}
I'm then calling this from my fragment :
private void searchDatabase(String searchString) throws ExecutionException,
InterruptedException {
List<DatabaseContacts> searchedContacts =
contactViewModel.getContactByName("%" + searchString + "%");
ArrayList<DatabaseContacts> contactsArrayList = new ArrayList<>();
if (searchedContacts != null){
contactsArrayList.addAll(searchedContacts);
contactsAdapter.refreshMyList(contactsArrayList);
}
}
and this is called from an on search query text changed method in my onCreateOptionsMenu :
#Override
public boolean onQueryTextChange(String newText) {
try {
searchDatabase(newText);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
but it just does nothing my original recyclerview contents never change any ideas?
you can use Transformation.switchMap to do search operations.
In viewmodel create MutableLiveData which has latest search string.
Inside viewmodel use:
LiveData<Data> data =
LiveDataTransformations.switchMap(searchStringLiveData, string ->
repo.loadData(string)))
Return the above live data to activity so it can observe and update view.
I faced the same issue and I managed to fix it using
switchMap
and
MutableLiveData
We just need to use MutableLiveData to set the current value of editText, and when the user search we call setValue(editText.getText())
public class FavoriteViewModel extends ViewModel {
public LiveData<PagedList<TeamObject>> teamAllList;
public MutableLiveData<String> filterTextAll = new MutableLiveData<>();
public void initAllTeams(TeamDao teamDao) {
this.teamDao = teamDao;
PagedList.Config config = (new PagedList.Config.Builder())
.setPageSize(10)
.build();
teamAllList = Transformations.switchMap(filterTextAll, input -> {
if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
return new LivePagedListBuilder<>(
teamDao.loadAllTeam(), config)
.build();
} else {
System.out.println("CURRENTINPUT: " + input);
return new LivePagedListBuilder<>(
teamDao.loadAllTeamByName(input), config)
.build();
}
});
}
}
in Activity of fragment
viewModel = ViewModelProviders.of(activity).get(FavoriteViewModel.class);
viewModel.initAllTeams(AppDatabase.getInstance(activity).teamDao());
FavoritePageListAdapter adapter = new FavoritePageListAdapter(activity);
viewModel.teamAllList.observe(
activity, pagedList -> {
try {
Log.e("Paging ", "PageAll" + pagedList.size());
try {
//to prevent animation recyclerview when change the list
recycleFavourite.setItemAnimator(null);
((SimpleItemAnimator) Objects.requireNonNull(recycleFavourite.getItemAnimator())).setSupportsChangeAnimations(false);
} catch (Exception e) {
}
adapter.submitList(pagedList);
} catch (Exception e) {
}
});
recycleFavourite.setAdapter(adapter);
//first time set an empty value to get all data
viewModel.filterTextAll.setValue("");
edtSearchFavourite.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
#Override
public void afterTextChanged(Editable editable) {
//just set the current value to search.
viewModel.filterTextAll.setValue("%" + editable.toString() + "%");
}
});
Room Dao
#Dao
public interface TeamDao {
#Query("SELECT * FROM teams order by orders")
DataSource.Factory<Integer, TeamObject> loadAllTeam();
#Query("SELECT * FROM teams where team_name LIKE :name or LOWER(team_name_en) like LOWER(:name) order by orders")
DataSource.Factory<Integer, TeamObject> loadAllTeamByName(String name);
}
PageListAdapter
public class FavoritePageListAdapter extends PagedListAdapter<TeamObject, FavoritePageListAdapter.OrderHolder> {
private static DiffUtil.ItemCallback<TeamObject> DIFF_CALLBACK =
new DiffUtil.ItemCallback<TeamObject>() {
// TeamObject details may have changed if reloaded from the database,
// but ID is fixed.
#Override
public boolean areItemsTheSame(TeamObject oldTeamObject, TeamObject newTeamObject) {
System.out.println("GGGGGGGGGGGOTHERE1: " + (oldTeamObject.getTeam_id() == newTeamObject.getTeam_id()));
return oldTeamObject.getTeam_id() == newTeamObject.getTeam_id();
}
#Override
public boolean areContentsTheSame(TeamObject oldTeamObject,
#NonNull TeamObject newTeamObject) {
System.out.println("GGGGGGGGGGGOTHERE2: " + (oldTeamObject.equals(newTeamObject)));
return oldTeamObject.equals(newTeamObject);
}
};
private Activity activity;
public FavoritePageListAdapter() {
super(DIFF_CALLBACK);
}
public FavoritePageListAdapter(Activity ac) {
super(DIFF_CALLBACK);
this.activity = ac;
}
#NonNull
#Override
public OrderHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_favourite, parent, false);
return new FavoritePageListAdapter.OrderHolder(view);
}
#Override
public void onBindViewHolder(#NonNull OrderHolder holder,
int position) {
System.out.println("GGGGGGGGGGGOTHERE!!!");
if (position <= -1) {
return;
}
TeamObject teamObject = getItem(position);
try {
holder.txvTeamRowFavourite.setText(teamObject.getTeam_name());
} catch (Exception e) {
e.printStackTrace();
}
}
public class OrderHolder extends RecyclerView.ViewHolder {
private TextView txvTeamRowFavourite;
OrderHolder(View itemView) {
super(itemView);
txvTeamRowFavourite = itemView.findViewById(R.id.txv_team_row_favourite);
}
}
}
Here is a working example in KOTLIN
in the Fragment
binding.search.addTextChangedListener { text ->
viewModel.searchNameChanged(text.toString())
}
viewModel.customers.observe(this, Observer {
adapter.submitList(it)
binding.swipe.isRefreshing=false
})
search -> is my edit text
customers -> is the data list in the viewModel
View Model
private val _searchStringLiveData = MutableLiveData<String>()
val customers = Transformations.switchMap(_searchStringLiveData){string->
repository.getCustomerByName(string)
}
init {
refreshCustomers()
_searchStringLiveData.value=""
}
fun searchNameChanged(name:String){
_searchStringLiveData.value=name
}
I faced the same issue and solved it with the answer of #Rohit, thanks! I simplified my solution a bit to illustrate it better. There are Categories and each Category has many Items. The LiveData should only return items from one Category. The user can change the Category and then the fun search(id: Int) is called, which changes the value of a MutableLiveData called currentCategory. This then triggers the switchMap and results in a new query for items of the category:
class YourViewModel: ViewModel() {
// stores the current Category
val currentCategory: MutableLiveData<Category> = MutableLiveData()
// the magic happens here, every time the value of the currentCategory changes, getItemByCategoryID is called as well and returns a LiveData<Item>
val items: LiveData<List<Item>> = Transformations.switchMap(currentCategory) { category ->
// queries the database for a new list of items of the new category wrapped into a LiveData<Item>
itemDao.getItemByCategoryID(category.id)
}
init {
currentCategory.value = getStartCategoryFromSomewhere()
}
fun search(id: Int) { // is called by the fragment when you want to change the category. This can also be a search String...
currentCategory.value?.let { current ->
// sets a Category as the new value of the MutableLiveData
current.value = getNewCategoryByIdFromSomeWhereElse(id)
}
}
}
I implement the bar code searching product using the following approach.
Everytime the value of productBarCode changes, the product will be searched in the room db.
#AppScoped
class PosMainViewModel #Inject constructor(
var localProductRepository: LocalProductRepository) : ViewModel() {
val productBarCode: MutableLiveData<String> = MutableLiveData()
val product: LiveData<LocalProduct> = Transformations.switchMap(productBarCode) { barcode ->
localProductRepository.getProductByBarCode(barcode)
}
init {
productBarCode.value = ""
}
fun search(barcode: String) {
productBarCode.value = barcode
}}
In activity
posViewModel.product.observe(this, Observer {
if (it == null) {
// not found
} else {
productList.add(it)
rvProductList.adapter!!.notifyDataSetChanged()
}
})
for searching
posViewModel.search(barcode) //search param or barcode
I am learning Observer pattern, I want my observable to keep track of a certain variable when it changes it's value and do some operations, I've done something like :
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Observable<Integer> mObservable = Observable.just(VARIABLE_TO_OBSERVE);
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
But doMethod2() doesn't get called
Nothing is magic in the life : if you update a value, your Observable won't be notified. You have to do it by yourself. For example using a PublishSubject.
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Subject<Integer> mObservable = PublishSubject.create();
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
// notify the Observable that the value just change
mObservable.onNext(VARIABLE_TO_OBSERVE);
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
If interested here a Kotlin version of Variable class, which lets subscribers to be updated after every variable change.
class Variable<T>(private val defaultValue: T) {
var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
val observable = BehaviorSubject.createDefault(value)
}
Usage:
val greeting = Variable("Hello!")
greeting.observable.subscribe { Log.i("RxKotlin", it) }
greeting.value = "Ciao!"
greeting.value = "Hola!"
This will print:
"Hello!"
"Ciao!"
"Hola!"
#dwursteisen Nothing is magic, no, but I think we can get it a little more magic than that... 😊
How about using an Rx BehaviourSubject in this way:
import rx.functions.Action1;
import rx.subjects.BehaviorSubject;
public class BehaviourSubjectExample {
public BehaviourSubjectExample() {
subject.skip(1).subscribe(new Action1<Integer>() {
#Override
public void call(Integer integer) {
System.out.println("The value changed to " + integer );
}
});
}
public final BehaviorSubject<Integer> subject = BehaviorSubject.create(0);
public int getValue() { return subject.getValue(); }
public void setValue(int value) { subject.onNext(value); }
}
Remove the .skip(1) if you want the observing code to see the initial value.
The variable backing remains with the BehaviourSubject and can be accessed through conventional Java Getter/Setter. This is a toy example of course: If your use case were really this simple there'd be no excuse for not just writing:
private int value = 0;
public int getValue() { return value; }
public void setValue(int value) {
this.value = value;
System.out.println("The value changed to " + value );
}
...but the use of BehaviourSubject lets you bridge changes to other Rx data-streams inside your class for composing more advanced behaviours.