Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refresh data from a network using LiveData

I am working on an app that queries the github api to get a list of user and i'm following the recommended android architecture component guide. Once the data is fetched from the network, I store it locally using Room DB and then display it on the UI using ViewModel that observes on the LiveData object (this works fine). However, I want to be able to have a button which when clicked would trigger a refresh action and perform a network request to get new data from the API if and only if there is a network connection. The issue is when I click the button, two network calls are triggered, one from the refreshUserData() and the other one from the already existing LiveData that was triggered during onCreate(). How best should I handle this situation such that my refresh button performs only one network request and not two as is the case. Here's my Repository class:

public class UserRepository {
private final UserDao mUserDao;
private LiveData<List<GitItem>> mAllUsers;
private LiveData<GitItem> mUser;
private final GithubUserService githubUserService;
private final AppExecutors appExecutors;
private final Application application;
private static String LOG_TAG = UserRepository.class.getSimpleName();
private RateLimiter<String> repoListRateLimit = new RateLimiter<>(1, TimeUnit.MINUTES);

   public UserRepository(Application application, GithubUserService githubUserService, AppExecutors appExecutors) {
        this.application = application;
        UserRoomDatabase db = UserRoomDatabase.getDatabase(application);
        mUserDao = db.userDao();
        this.githubUserService = githubUserService;
        this.appExecutors = appExecutors;
    }

    public LiveData<GitItem> getUser(int userId) {
        LiveData<GitItem> user = mUserDao.loadUser(userId);
        Log.d(LOG_TAG, "retrieved user from database successful");
        return user;
    }

    public LiveData<Resource<List<GitItem>>> getAllUsers() {
        //ResultType, RequestType
        /**
         * List<GitItem> is the [ResultType]
         * GithubUser is the [RequestType]
         */
        return new NetworkBoundResource<List<GitItem>, GithubUser>(appExecutors) {

            @Override
            protected void saveCallResult(@NonNull GithubUser item) {
                Log.d(LOG_TAG, "call to insert results to db");
                mUserDao.insertUsers(item.getItems());
            }

            @Override
            protected boolean shouldFetch(@Nullable List<GitItem> data) {
                Log.d(LOG_TAG, "null?" + (data == null));
                Log.d(LOG_TAG, "empty? " + (data.isEmpty()));
                Log.d(LOG_TAG, "rate? " + (repoListRateLimit.shouldFetch("owner")));
                Log.d(LOG_TAG, "should fetch? " + (data.isEmpty() || repoListRateLimit.shouldFetch("owner")));
                return data.isEmpty() || data == null;

            }

            @NonNull
            @Override
            protected LiveData<List<GitItem>> loadFromDb() {
                Log.d(LOG_TAG, " call to load from db");
                return mUserDao.getAllUsers();
            }

            @NonNull
            @Override
            protected LiveData<ApiResponse<GithubUser>> createCall() {
                Log.d(LOG_TAG, "creating a call to network");
                return githubUserService.getGithubUsers("language:java location:port-harcourt");
            }

            @Override
            protected GithubUser processResponse(ApiResponse<GithubUser> response) {
                return super.processResponse(response);
            }
        }.asLiveData();
    }


    public LiveData<Resource<List<GitItem>>> refreshUserData() {
        //ResultType, RequestType
        /**
         * List<GitItem> is the [ResultType]
         * GithubUser is the [RequestType]
         */
        return new NetworkBoundResource<List<GitItem>, GithubUser>(appExecutors) {

            @Override
            protected void saveCallResult(@NonNull GithubUser item) {
                Log.d(LOG_TAG, "call to insert results to db");
                mUserDao.insertUsers(item.getItems());
            }

            @Override
            protected boolean shouldFetch(@Nullable List<GitItem> data) {
                Log.d(LOG_TAG, "refreshUserData");
                return true;
            }

            @NonNull
            @Override
            protected LiveData<List<GitItem>> loadFromDb() {
                Log.d(LOG_TAG, "refreshUserData");
                Log.d(LOG_TAG, " call to load from db");
                return mUserDao.getAllUsers();
            }

            @NonNull
            @Override
            protected LiveData<ApiResponse<GithubUser>> createCall() {
                Log.d(LOG_TAG, "refreshUserData");
                Log.d(LOG_TAG, "creating a call to network");
                return githubUserService.getGithubUsers("language:java location:port-harcourt");
            }

            @Override
            protected GithubUser processResponse(ApiResponse<GithubUser> response) {
                return super.processResponse(response);
            }
        }.asLiveData();
    }

    public Application getApplication() {
        return application;
    }
}

My ViewModel Class is:

public class UserProfileViewModel extends AndroidViewModel {
    private UserRepository mRepository;


    public UserProfileViewModel(UserRepository mRepository) {
        super(mRepository.getApplication());
        this.mRepository = mRepository;
      }

    public LiveData<Resource<List<GitItem>>> getmAllUsers() {
        return mRepository.getAllUsers();
      }

    public LiveData<Resource<List<GitItem>>> refreshUserData() {
        return mRepository.refreshUserData();
      }

    public LiveData<GitItem> getUser(int userId) {
        return mRepository.getUser(userId);
      }
    }

My MainActivity class:

    public class MainActivity extends AppCompatActivity implements GithubAdapter.ListItemClickListener {
    private RecyclerView mRecyclerView;
    private UserProfileViewModel mUserViewModel;
    public static final String USER_ID = "userId";
    private ConnectivityManager cm;
    private boolean isConnected;
    private UserRepository mRepository;
    private  GithubUserService mGithubUserService;

    private NetworkInfo activeNetwork;
    private Picasso mPicasso;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mGithubUserService = GithubApplication.get(MainActivity.this).getGithubUserService();
        mPicasso = GithubApplication.get(MainActivity.this).getPicasso();

        mRepository = new UserRepository(getApplication(), mGithubUserService, new AppExecutors());


        // the factory and its dependencies instead should be injected with DI framework like Dagger
        ViewModelFactory factory = new ViewModelFactory(mRepository);

        mUserViewModel = ViewModelProviders.of(this, factory).get(UserProfileViewModel.class);

        //  initViews();
        mRecyclerView = findViewById(R.id.users_recycler);
        final GithubAdapter mAdapter = new GithubAdapter(this, this, mPicasso);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

        getUsers(mAdapter);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //check if there is a network connection
                // if there is a network connection the LoaderManager is called but
                //  displays a message if there's no network connection
                activeNetwork = cm.getActiveNetworkInfo();
                isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
                if (isConnected) {
                    mUserViewModel.refreshUserData().observe(MainActivity.this, new Observer<Resource<List<GitItem>>>() {
                        @Override
                        public void onChanged(@Nullable Resource<List<GitItem>> listResource) {
//                            Toast.makeText(MainActivity.this, "second" + listResource.status, Toast.LENGTH_LONG).show();
                            Snackbar.make(view, "refresh:" + listResource.status, Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                            mAdapter.setUsers(listResource.data);
                        }
                    });

                } else {
                    Snackbar.make(view, "no connection", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();

                }

            }
        });


    }



 private void getUsers(GithubAdapter mAdapter) {
        mUserViewModel.getmAllUsers().observe(this, new Observer<Resource<List<GitItem>>>() {
            @Override
            public void onChanged(@Nullable Resource<List<GitItem>> listResource) {
                Toast.makeText(MainActivity.this, "" + listResource.status, Toast.LENGTH_LONG).show();
                mAdapter.setUsers(listResource.data);
            }
        });
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onListItemClick(int userId) {
        Intent detailIntent = new Intent(MainActivity.this, 
        DetailActivity.class);
        detailIntent.putExtra(USER_ID, userId);
        startActivity(detailIntent);
      }
    }

You can find the full code here

like image 941
Ikhiloya Imokhai Avatar asked Jan 14 '19 14:01

Ikhiloya Imokhai


1 Answers

The best way is to register your LiveData in the MainActivity in onCreate method and listen for db changes like you did. In the FAB onClick just make network request and save it to db without LiveData. LiveData in onCreate method will be triggered.

like image 86
Valgaal Avatar answered Oct 27 '22 09:10

Valgaal