0

I need write a service which will update the list in MainActivity every 30sec. I use MVVM with ViewModel and LiveData and so my Service class looks like this:

public class ArticleJobService extends JobService {

    public static String TAG = "ArticleJobService";

    private Context context = this;

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "onStartJob");
        MainActivity.PAGE_NUMBER++;
        LiveData<List<Article>> liveArticles = ArticleRepository.getInstance(getApplication()).getArticles(MainActivity.PAGE_NUMBER);

        liveArticles.observeForever(new Observer<List<Article>>() {
            @Override
            public void onChanged(@Nullable List<Article> articles) {
                Log.d(TAG, "onStartJob - onChanged!!!!!!");
                liveArticles.removeObserver(this);
                NotificationUtils.showNotification(context, articles.get(0).getSectionName(), articles.get(0).getWebTitle());
                jobFinished(jobParameters, true);
            }
        });
        return true;
    }
}

Class for my notification:

public static void showNotification(Context context, String section, String title) {
    PendingIntent contentPendingIntent = PendingIntent.getActivity
            (context, REQUEST_CODE, new Intent(context, MainActivity.class),
                    PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationManager manager =
            (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        manager.createNotificationChannel(createNotificationChannel(context));
    }

    NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle(section)
            .setContentText(title)
            .setContentIntent(contentPendingIntent)
            .setSmallIcon(R.drawable.app_icon)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setDefaults(NotificationCompat.DEFAULT_ALL)
            .setAutoCancel(true);

    manager.notify(0, builder.build());

}

When Onchanged in JobService works I get the list and show a notification. Notification opens MainActivity which makes new call to api as it always did. What changes do I have to make in order the MainActivity to show the list that I got from the service??? I really can't tie this up together. I heard of IPC but wouldn't do that, I want some simpler practice which I sure exists which I just don't know about. Also, there are two cases: Notification came and MainActivity is open, app is open but MainActivity is not in the foreground and app is on the background or closed. How should I handle each of these cases?

See also piece of code from MainActivity onCreate:

 mArticleViewModel = ViewModelProviders.of(this).get(ArticleViewModel.class);
    mArticleViewModel.getArticleList(PAGE_NUMBER).observe(this, articles -> {
        Log.d(TAG, "List<Result> onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        mProgressBar.setVisibility(View.GONE);
        mProgressBarMain.setVisibility(View.GONE);
        mIsLoading = false;
        mArticles = articles;

Please provide the best practices for this task, I know it's very common I just do it first time and using LiveData makes it way more complicated.

Here is Also Repository code:

public static ArticleRepository getInstance(Application application){
        if(INSTANCE == null){
            return  new ArticleRepository(application);
        }

        return INSTANCE;
    }

    private ArticleRepository(Application application) {
        Log.d(TAG, "ArticleRepository constructor");
        mContext = application;
        mArticles = new MutableLiveData<>();
        ArticleRoomDatabase db = ArticleRoomDatabase.getInstance(application);
        mArticleDao = db.articleDao();
    }

    public LiveData<List<Article>> getArticles(int page) {
        Log.d(TAG, "getArticles");
        if (NetworkUtils.isOnline(mContext)) {
            Log.d(TAG, "isOnline");
            mArticles = loadFromNetwork(page);
        } else {
            Log.d(TAG, "is NOT Online");
            mArticles = loadFromDB(page);
        }
    }
sunflower20
  • 469
  • 3
  • 8
  • 20
  • `I use MVVM with ViewModel and LiveData and so my Service class looks like this:` i do not understand why your Service class looks like that, at all – EpicPandaForce Jul 01 '18 at 10:29
  • If I could do better I wont ask this question and ask for suggestions, so either make suggestions and corrections, being very specific or not make any comment please – sunflower20 Jul 01 '18 at 10:54
  • What I mean is, what exactly does your service do, beyond fetching a list of items in your service, then shows a notification? I would imagine that "updating a list" means fetching data that is written to Room, and once that's done then a subscription to `LiveData>` would update the list automatically without you having to do anything beyond doing the write on a background thread. Your requirements are unclear. – EpicPandaForce Jul 01 '18 at 11:10
  • So the requirement is to run background service that fetches data from webservice every 30 sec and shows notification, opening notification shows updated homescreen. What my service does now is only gets data from the server and I don't understand how to tie it with MainActivity. Regarding room, it is not a source of truth in my app and I had problems inserting into Room with LiveData so I use Room with common List, I will provide repo code either – sunflower20 Jul 01 '18 at 11:26
  • Edited question and added repo code – sunflower20 Jul 01 '18 at 11:30

1 Answers1

1

You have this problem specifically because your Repository implementation is incorrect.

public LiveData<List<Article>> getArticles(int page) {
    Log.d(TAG, "getArticles");
    if (NetworkUtils.isOnline(mContext)) {
        Log.d(TAG, "isOnline");
        mArticles = loadFromNetwork(page);
    } else {
        Log.d(TAG, "is NOT Online");
        mArticles = loadFromDB(page);
    }
}

If you check the code for NetworkBoundResource, the trick is that you have a single LiveData that binds together the ability to both load from network, and to load from database.

In your case, you are replacing the database's auto-updating query results whenever you have network access - which is why you can't update the MainActivity.

The easiest way (without using a MediatorLiveData) is to have two separate functions on Repository: one for fetchFromNetwork, and one for fetchFromDatabase. The MainActivity should always fetch from database, while the Service always triggers load from network (and inserts it directly into database via a Dao).

This way, the observe function in MainActivity will receive the latest data when Service inserts the data into DB on background thread.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Thank you a lot, I was thinking about making separate calls to db and webservice, but you made it more clear and I feel more confident. The only thing that concerns me is how then I should organize pageing, currently it loads from network every time list end is reached and I clear DB every time it reaches page size * 2 – sunflower20 Jul 01 '18 at 14:42
  • If there is paging involved, you might want to expose `DataSource.Factory` from your DAO, and set a BoundaryCallback on your LivePagedListBuilder (using Paging AAC). That way you can start a background fetch task when you have scrolled to the bottom of the page. – EpicPandaForce Jul 01 '18 at 14:50
  • I am very afraid I spent on Paging Library 3-4 days and couldn't get it work and my deadline is tomorrow, so I think I will implement the paging library concept by hand. – sunflower20 Jul 01 '18 at 14:58
  • Hmm if you gave up on Paging then you can use `object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (!recyclerView.canScrollVertically(1)) { onScrolledToBottom() } }` to detect when you are at bottom of page, and initiate a background fetch task (if it is not yet in progress) – EpicPandaForce Jul 01 '18 at 15:04
  • I already did that part, reaching the bottom and fetching data from network, my api does provide page number and page size, my concern is fetching data from db page by page as it's done in pageing library. – sunflower20 Jul 01 '18 at 15:42
  • In that case if you already have a fetch task that runs on background thread and writes into DB, then if you observe only `LiveData>` that comes from the DAO (in your activity), then you'll be done with paging as your `observe` will receive new state evaluated by Dao – EpicPandaForce Jul 01 '18 at 16:00