I have a list of different mines. Each mine has a list of blocks.
I have the mines in a spinner and the blocks in a recyclerview.
I want to display the different lists of blocks whenever the user changes the mine in the mine spinner
I am using Firebase in the backend as my database.
When I change the mine in the spinner, I update the block list by creating a new MutableLiveData which I've extended in a class called FirebaseQueryLiveData
The first time that I initialise the FirebaseQueryLiveData with the quesry containing the mine name, all the events inside it fire. However, after that, I call it and nothing fires. It breaks in the constructor if I have a breakpoint there, but it never reaches the run() method, onActive() method or the onDataChanged in the ValueEventListener.
I have done some research, and I have seen suggestions to replace the LiveData with MutableLiveData. I've done this, and it doesn't seem to make a difference.
Can anyone see anything in the code which I might be missing? I'm not very familiar with the android architecture components and I got the FirebaseQueryLiveData class from another helpful website with a tutorial, so I'm battling to understand where I have gone wrong.
I have done some research, and I have seen suggestions to replace the LiveData with MutableLiveData. I've done this, and it doesn't seem to make a difference.
public class BlockListActivityViewModel extends ViewModel {
private static DatabaseReference blockOutlineRef; // = FirebaseDatabase.getInstance().getReference(FireBasePaths.BLOCKOUTLINE.getPath("Therisa"));
private static DatabaseReference mineListRef;
private FirebaseQueryLiveData blockOutlineLiveDataQuery = null;
private LiveData<BlockOutlineList> blockOutlineLiveData = null;
private MediatorLiveData<String> selectedBlockNameMutableLiveData;
private MediatorLiveData<ArrayList<String>> mineListMutableLiveData;
public BlockListActivityViewModel() {
User loggedInUser = UserSingleton.getInstance();
setUpFirebasePersistance();
setupMineLiveData(loggedInUser);
// setupBlockOutlineListLiveData();
}
private void setupBlockOutlineListLiveData(String mineName) {
if (mineName != "") {
blockOutlineRef = FirebaseDatabase.getInstance().getReference(FireBasePaths.BLOCKOUTLINE.getPath(mineName));
blockOutlineLiveDataQuery = new FirebaseQueryLiveData(blockOutlineRef);
blockOutlineLiveData = Transformations.map(blockOutlineLiveDataQuery, new BlockOutlineHashMapDeserialiser());
}
}
private void setupMineLiveData(User user) {
ArrayList<String> mineNames = new ArrayList<>();
if (user != null) {
if (user.getWriteMines() != null) {
for (String mineName : user.getWriteMines().values()) {
mineNames.add(mineName);
}
}
}
setMineListMutableLiveData(mineNames);
if (mineNames.size() > 0) {
updateMineLiveData(mineNames.get(0));
}
}
public void updateMineLiveData(String mineName) {
SelectedMineSingleton.setMineName(mineName);
setupBlockOutlineListLiveData(SelectedMineSingleton.getInstance());
}
public void setUpFirebasePersistance() {
int i = 0;
// FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
private MutableLiveData<NamedBlockOutline> selectedBlockOutlineMutableLiveData;
public MutableLiveData<NamedBlockOutline> getSelectedBlockOutlineMutableLiveData() {
if (selectedBlockOutlineMutableLiveData == null) {
selectedBlockOutlineMutableLiveData = new MutableLiveData<>();
}
return selectedBlockOutlineMutableLiveData;
}
public void setSelectedBlockOutlineMutableLiveData(NamedBlockOutline namedBlockOutline) {
getSelectedBlockOutlineMutableLiveData().postValue(namedBlockOutline);
}
public MediatorLiveData<String> getSelectedBlockNameMutableLiveData() {
if (selectedBlockNameMutableLiveData == null)
selectedBlockNameMutableLiveData = new MediatorLiveData<>();
return selectedBlockNameMutableLiveData;
}
public void setSelectedBlockNameMutableLiveData(String blockName) {
selectedBlockNameMutableLiveData.postValue(blockName);
}
public MediatorLiveData<ArrayList<String>> getMineListMutableLiveData() {
if (mineListMutableLiveData == null)
mineListMutableLiveData = new MediatorLiveData<>();
return mineListMutableLiveData;
}
public void setMineListMutableLiveData(ArrayList<String> mineListString) {
getMineListMutableLiveData().postValue(mineListString);
}
private class BlockOutlineHashMapDeserialiser implements Function<DataSnapshot, BlockOutlineList>, android.arch.core.util.Function<DataSnapshot, BlockOutlineList> {
#Override
public BlockOutlineList apply(DataSnapshot dataSnapshot) {
BlockOutlineList blockOutlineList = new BlockOutlineList();
HashMap<String, NamedBlockOutline> blockOutlineStringHashMap = new HashMap<>();
for (DataSnapshot childData : dataSnapshot.getChildren()) {
NamedBlockOutline thisNamedOutline = new NamedBlockOutline();
HashMap<String, Object> blockOutlinePointHeader = (HashMap<String, Object>) childData.getValue();
HashMap<String, BlockPoint> blockOutlinePoints = (HashMap<String, BlockPoint>) blockOutlinePointHeader.get("blockOutlinePoints");
thisNamedOutline.setBlockName(childData.getKey());
thisNamedOutline.setBlockOutlinePoints(blockOutlinePoints);
blockOutlineStringHashMap.put(childData.getKey(), thisNamedOutline);
}
blockOutlineList.setBlockOutlineHashMap(blockOutlineStringHashMap);
return blockOutlineList;
}
}
#NonNull
public LiveData<BlockOutlineList> getBlockOutlineLiveData() {
return blockOutlineLiveData;
}
}
LiveData
public class FirebaseQueryLiveData extends MutableLiveData<DataSnapshot> {
private static final String LOG_TAG = "FirebaseQueryLiveData";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
private boolean listenerRemovePending = false;
private final Handler handler = new Handler();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
public FirebaseQueryLiveData(DatabaseReference ref) {
this.query = ref;
}
private final Runnable removeListener = new Runnable() {
#Override
public void run() {
query.removeEventListener(listener);
listenerRemovePending = false;
Log.d(LOG_TAG, "run");
}
};
#Override
protected void onActive() {
super.onActive();
if (listenerRemovePending) {
handler.removeCallbacks(removeListener);
Log.d(LOG_TAG, "listenerRemovePending");
}
else {
query.addValueEventListener(listener);
Log.d(LOG_TAG, "addValueEventListener");
}
listenerRemovePending = false;
Log.d(LOG_TAG, "listenerRemovePending");
}
#Override
protected void onInactive() {
super.onInactive();
// Listener removal is schedule on a two second delay
handler.postDelayed(removeListener, 4000);
listenerRemovePending = true;
Log.d(LOG_TAG, "listenerRemovePending");
}
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());
}
}
}
Related
Currently this line "observer = data -> createTokenMap(data.getPositions());" calls a function and do some logic.
How can I accept a return value from this function and pass it into another function and so on.
public class PositionViewModel extends AndroidViewModel {
private final LiveData<PositionResponse> positionResponseLiveData;
private final Observer<PositionResponse> observer;
private final Map<String, Long> tokenMap = new HashMap<>();
public PositionViewModel(#NonNull Application application) {
super(application);
observer = data -> createTokenMap(data.getPositions());
positionResponseLiveData = PositionRepository.getInstance().getPositions();
positionResponseLiveData.observeForever(observer);
}
public LiveData<PositionResponse> getPositions() {
return positionResponseLiveData;
}
private void createTokenMap(Positions positions) {
for (Net position : positions.getNet()) {
tokenMap.put(position.getTradingsymbol(), position.getInstrument_token());
}
for (Day position : positions.getDay()) {
tokenMap.put(position.getTradingsymbol(), position.getInstrument_token());
}
}
public Map<String, Long> getTokenMap() {
return tokenMap;
}
#Override
protected void onCleared() {
super.onCleared();
positionResponseLiveData.removeObserver(this.observer);
}
}
Any help will be highly appreciated.
Even though I am using ViewModel, whenever the device is rotated, the data in the Recyclerview disappears. I had to put the makeSearch() method inside the onClick() method because I need to get the text that the button grabs and use it as the search parameter. Is there a better way I can handle this to avoid this problem? My code is right here:
SearchActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
// What happens when the search button is clicked
materialButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (Objects.requireNonNull(textInputEditText.getText()).toString().isEmpty()) {
textInputEditText.setError("Type a search query");
} else {
mSearchInput = Objects.requireNonNull(textInputEditText.getText()).toString();
textInputEditText.setText("");
makeSearch();
}
}
});
}
// Gets the ViewModel, Observes the Question LiveData and delivers it to the Recyclerview
private void makeSearch() {
final SearchAdapter searchAdapter = new SearchAdapter();
SearchViewModel mSearchViewModel = new ViewModelProvider(this,
new CustomSearchViewModelFactory(new SearchRepository())).get(SearchViewModel.class);
mSearchViewModel.setQuery(mSearchInput);
mSearchViewModel.getQuestionLiveData().observe(this, new Observer<List<Question>>() {
#Override
public void onChanged(List<Question> questions) {
mQuestions = questions;
searchAdapter.setQuestions(questions);
}
});
mRecyclerView.setAdapter(searchAdapter);
searchAdapter.setOnClickListener(mOnClickListener);
}
SearchViewModel:
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<>();
private LiveData<List<Question>> mQuestionLiveData = Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
SearchViewModel(SearchRepository searchRepository) {
this.mSearchRepository = searchRepository;
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
}
SearchRepository:
public class SearchRepository {
//private String inTitle;
private MutableLiveData<List<Question>> mQuestions = new MutableLiveData<>();
public SearchRepository() {
//getQuestionsWithTextInTitle();
}
private void getQuestionsWithTextInTitle(String inTitle) {
ApiService apiService = RestApiClient.getApiService(ApiService.class);
Call<QuestionsResponse> call = apiService.getQuestionsWithTextInTitle(inTitle);
call.enqueue(new Callback<QuestionsResponse>() {
#Override
public void onResponse(Call<QuestionsResponse> call, Response<QuestionsResponse> response) {
QuestionsResponse questionsResponse = response.body();
if (questionsResponse != null) {
mQuestions.postValue(questionsResponse.getItems());
//shouldShowData = true;
} else {
Log.d("SearchRepository", "No matching question");
//shouldShowData = false;
}
}
#Override
public void onFailure(Call<QuestionsResponse> call, Throwable t) {
//shouldShowData = false;
t.printStackTrace();
}
});
}
public LiveData<List<Question>> getQuestions(String inTitle) {
getQuestionsWithTextInTitle(inTitle);
return mQuestions;
}
}
Your approach of passing the search input in through your CustomSearchViewModelFactory and into the constructor for the ViewModel and into the constructor for your SearchRepository isn't going to work in any case. While the first time you search your CustomSearchViewModelFactory creates the ViewModel, the second time you hit search, your SearchViewModel is already created and your factory is not invoked a second time, meaning you never get the second query.
Instead, you should file the ViewModel Overview documentation, and use Transformations.switchMap() to convert your input (the search string) into a new LiveData<List<Question>> for that given query.
This means that your ViewModel would look something like
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<String>();
private LiveData<List<Question>> mQuestionLiveData =
Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
public SearchViewModel() {
mSearchRepository = new SearchRepository();
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
}
You'd then update your Activity to:
Always observe the getQuestionLiveData() (note that you won't get a callback to your Observer until you actually set the first query)
Call setQuery() on your SearchViewModel in your makeSearch()
Remove your CustomSearchViewModelFactory entirely (it would no longer be needed).
The problem is that it always gives "List:NULL" Toast in MainActivity and list is not passed I suppose. Please help, trying for 2 days. Help me!!
I am confused as to how I should pass the ArrayList from Firebase database to my MainActivity.
Before doing the below thing, I tried to pass ArrayList in onCreate of Splash activity after it fetched from firebase but myWebLinks always came null when I used in onCreate except on onDataChange of ValueEventListener so I couldn't pass in next activity.
This is my SplashActivity.class
public class SplashActivity extends AppCompatActivity {
private static final String TAG = "abcd";
//1 Firebase database object
//entry point for the app to access our database
private FirebaseDatabase mFirebaseDatabase;
//2 is a class that reference a specific part of the database, references 'link' portion of DB
private DatabaseReference mDatabaseReference;
//To read from DB, attach ChildEventListener object to the reference. Allows to listen and have code triggered whenever
//changes occur on the node.
private ValueEventListener mValueEventListener;
private AVLoadingIndicatorView aviIV;
private ArrayList<WebLinks> mywebLinks;
public GenericTypeIndicator<ArrayList<WebLinks>> genericTypeIndicator;
private long seed;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
aviIV = findViewById(R.id.avi);
aviIV.smoothToShow();
mFirebaseDatabase = FirebaseDatabase.getInstance();
mDatabaseReference = mFirebaseDatabase.getReference().child("weblinks");
if (mValueEventListener == null) {
valueEventListener();
}
}
#Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause: ");
//14 Removing
if (mValueEventListener != null) {
mDatabaseReference.removeEventListener(mValueEventListener);
mValueEventListener = null;
}
}
#Override
protected void onResume() {
super.onResume();
if (mValueEventListener == null) {
valueEventListener();
mDatabaseReference.addValueEventListener(mValueEventListener);
}
}
void valueEventListener() {
final long[] value = {0};
mValueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
/* This method is called once with the initial value and again whenever data at this location is updated.*/
value[0] = dataSnapshot.getChildrenCount();
genericTypeIndicator = new GenericTypeIndicator<ArrayList<WebLinks>>() {
};
mywebLinks = dataSnapshot.getValue(genericTypeIndicator);
seed = System.nanoTime();
Collections.shuffle(mywebLinks, new Random(seed));
Toast.makeText(SplashActivity.this, "" + mywebLinks.get(0).getName(), Toast.LENGTH_SHORT).show();
Log.i(TAG, "getWebLinksFirebaseeee: on");
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putParcelableArrayListExtra("web_links", mywebLinks);
startActivity(intent);
SplashActivity.this.finish();
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException());
}
};
mDatabaseReference.addValueEventListener(mValueEventListener);
}
}
This is my MainActivity.class
public class MainActivity extends AppCompatActivity {
private WebView myWebView;
private WaveSwipeRefreshLayout mWaveSwipeRefreshLayout;
private Toolbar mtoolBar;
private ShineButton mFavBtn;
private BoomMenuButton leftBmb;
public static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
leftBmb = (BoomMenuButton) findViewById(R.id.action_bar_left_bmb);
mFavBtn = (ShineButton) findViewById(R.id.favBtn);
mFavBtn.init(this);
mtoolBar = (Toolbar) findViewById(R.id.toolBar);
setSupportActionBar(mtoolBar);
if (getSupportActionBar() != null) {
getSupportActionBar().setElevation(0);
}
ArrayList<WebLinks> myList = getIntent().getParcelableExtra("web_links");
if(myList==null)
Toast.makeText(this, "LIST:NULL", Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, "LIST:"+myList.get(1).getName(), Toast.LENGTH_SHORT).show();
myWebView = findViewById(R.id.webView);
initWebSettings();
initSwipeRefresh();
initLeftBtn();
}
This is my class
public class WebLinks implements Parcelable {
private String name;
private String link;
public WebLinks() {
}
public WebLinks(String webName, String webLink) {
this.name = webName;
this.link = webLink;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public static final Parcelable.Creator<WebLinks> CREATOR = new Creator<WebLinks>() {
public WebLinks createFromParcel(Parcel source) {
WebLinks fields = new WebLinks();
fields.name = source.readString();
fields.link = source.readString();
return fields;
}
public WebLinks[] newArray(int size) {
return new WebLinks[size];
}
};
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(name);
parcel.writeString(link);
}
}
Recently I was trying this:
I have a list of jobs backed by data source (I am using paging library) and each item in job list is having a save button, and that save button updates the status of the job from unsaved to saved (or vice versa) in database and once updated it invalidates the DataSource, now that invalidation should cause reload for the current page immediately, but that isn't happening.
I checked values in database they actually get updated but that isn't the case with the UI.
Code:
public class JobsPagedListProvider {
private JobListDataSource<JobListItemEntity> mJobListDataSource;
public JobsPagedListProvider(JobsRepository jobsRepository) {
mJobListDataSource = new JobListDataSource<>(jobsRepository);
}
public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
return new LivePagedListProvider<Integer, JobListItemEntity>() {
#Override
protected DataSource<Integer, JobListItemEntity> createDataSource() {
return mJobListDataSource;
}
};
}
public void setQueryFilter(String query) {
mJobListDataSource.setQuery(query);
}
}
Here is my custom datasource:
public class JobListDataSource<T> extends TiledDataSource<T> {
private final JobsRepository mJobsRepository;
private final InvalidationTracker.Observer mObserver;
String query = "";
#Inject
public JobListDataSource(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
mJobsRepository.setJobListDataSource(this);
mObserver = new InvalidationTracker.Observer(JobListItemEntity.TABLE_NAME) {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
invalidate();
}
};
jobsRepository.addInvalidationTracker(mObserver);
}
#Override
public boolean isInvalid() {
mJobsRepository.refreshVersionSync();
return super.isInvalid();
}
#Override
public int countItems() {
return DataSource.COUNT_UNDEFINED;
}
#Override
public List<T> loadRange(int startPosition, int count) {
return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}
public void setQuery(String query) {
this.query = query;
}
}
Here is the code in JobsRepository that updates job from unsaved to saved:
public void saveJob(JobListItemEntity entity) {
Completable.fromCallable(() -> {
JobListItemEntity newJob = new JobListItemEntity(entity);
newJob.isSaved = true;
mJobDao.insert(newJob);
Timber.d("updating entity from " + entity.isSaved + " to "
+ newJob.isSaved); //this gets printed in log
//insertion in db is happening as expected but UI is not receiving new list
mJobListDataSource.invalidate();
return null;
}).subscribeOn(Schedulers.newThread()).subscribe();
}
Here is the Diffing logic for job list:
private static final DiffCallback<JobListItemEntity> DIFF_CALLBACK = new DiffCallback<JobListItemEntity>() {
#Override
public boolean areItemsTheSame(#NonNull JobListItemEntity oldItem, #NonNull JobListItemEntity newItem) {
return oldItem.jobID == newItem.jobID;
}
#Override
public boolean areContentsTheSame(#NonNull JobListItemEntity oldItem, #NonNull JobListItemEntity newItem) {
Timber.d(oldItem.isSaved + " comp with" + newItem.isSaved);
return oldItem.jobID == newItem.jobID
&& oldItem.jobTitle.compareTo(newItem.jobTitle) == 0
&& oldItem.isSaved == newItem.isSaved;
}
};
JobListDataSource in JobRepository (only relevant portion is mentioned below):
public class JobsRepository {
//holds an instance of datasource
private JobListDataSource mJobListDataSource;
//setter
public void setJobListDataSource(JobListDataSource jobListDataSource) {
mJobListDataSource = jobListDataSource;
}
}
getJobs() in JobsRepository:
public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
if (!isJobListInit) {
Observable<JobList> jobListObservable = mApiService.getOpenJobList(
mRequestJobList.setPageNo(startPosition/count + 1)
.setMaxResults(count)
.setSearchKeyword(query));
List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);
//make a synchronous network call since we have no data in db to return
if(jobs.size() == 0) {
JobList jobList = jobListObservable.blockingSingle();
updateJobList(jobList, startPosition);
} else {
//make an async call and return cached version meanwhile
jobListObservable.subscribe(new Observer<JobList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(JobList jobList) {
updateJobList(jobList, startPosition);
}
#Override
public void onError(Throwable e) {
Timber.e(e);
}
#Override
public void onComplete() {
}
});
}
}
return mJobDao.getJobsLimitOffset(count, startPosition);
}
updateJobList in jobsRepository:
private void updateJobList(JobList jobList, int startPosition) {
JobListItemEntity[] jobs = jobList.getJobsData();
mJobDao.insert(jobs);
mJobListDataSource.invalidate();
}
After reading the source code of DataSource I realized this:
A DataSource once invalidated will never become valid again.
invalidate() says: If invalidate has already been called, this method does nothing.
I was actually having a singleton of my custom DataSource (JobListDataSource) provided by JobsPagedListProvider, so when I was invalidating my DataSource in saveJob() (defined in JobsRepository), it was trying to get new DataSource instance (to fetch latest data by again calling loadRange() - that's how refreshing a DataSource works)
but since my DataSource was singleton and it was already invalid so no loadRange() query was being made!
So make sure you don't have a singleton of DataSource and invalidate your DataSource either manually (by calling invalidate()) or have a InvalidationTracker in your DataSource's constructor.
So the final solution goes like this:
Don't have a singleton in JobsPagedListProvider:
public class JobsPagedListProvider {
private JobListDataSource<JobListItemEntity> mJobListDataSource;
private final JobsRepository mJobsRepository;
public JobsPagedListProvider(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
}
public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
return new LivePagedListProvider<Integer, JobListItemEntity>() {
#Override
protected DataSource<Integer, JobListItemEntity> createDataSource() {
//always return a new instance, because if DataSource gets invalidated a new instance will be required(that's how refreshing a DataSource works)
mJobListDataSource = new JobListDataSource<>(mJobsRepository);
return mJobListDataSource;
}
};
}
public void setQueryFilter(String query) {
mJobListDataSource.setQuery(query);
}
}
Also make sure if you're fetching data from network you need to have right logic to check whether data is stale before querying the network else it will requery everytime the DataSource gets invalidated.
I solved it by having a insertedAt field in JobEntity which keeps track of when this item was inserted in DB and checking if it is stale in getJobs() of JobsRepository.
Here is the code for getJobs():
public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
Observable<JobList> jobListObservable = mApiService.getOpenJobList(
mRequestJobList.setPageNo(startPosition / count + 1)
.setMaxResults(count)
.setSearchKeyword(query));
List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);
//no data in db, make a synchronous call to network to get the data
if (jobs.size() == 0) {
JobList jobList = jobListObservable.blockingSingle();
updateJobList(jobList, startPosition, false);
} else if (shouldRefetchJobList(jobs)) {
//data available in db, so show a cached version and make async network call to update data
jobListObservable.subscribe(new Observer<JobList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(JobList jobList) {
updateJobList(jobList, startPosition, true);
}
#Override
public void onError(Throwable e) {
Timber.e(e);
}
#Override
public void onComplete() {
}
});
}
return mJobDao.getJobsLimitOffset(count, startPosition);
}
Finally remove InvalidationTracker in JobListDatasource as we are handling invalidation manually:
public class JobListDataSource<T> extends TiledDataSource<T> {
private final JobsRepository mJobsRepository;
String query = "";
public JobListDataSource(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
mJobsRepository.setJobListDataSource(this);
}
#Override
public int countItems() {
return DataSource.COUNT_UNDEFINED;
}
#Override
public List<T> loadRange(int startPosition, int count) {
return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}
public void setQuery(String query) {
this.query = query;
}
}
I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}