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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With