Fetching data with retrofit2 and saving in room - android

I am using retrofit2 for fetching data from the server and after fetching saving data in room database and then showing in recycler view. But it is no displayed (my data what I get using retrofit). I try display it in my fragment. In file ...data/data/databese/source.db these data are saved. I see it. So, that means that my code works. But I can't understand why it is not displayed.
my database class:
#Database(entities = {Source.class}, exportSchema = false, version = 1)
public abstract class SourceDatabase extends RoomDatabase {
private static final String DB_NAME = "source.db";
public abstract SourceDao sourceDao();
private static SourceDatabase instance;
public static SourceDatabase getInstance(Context context) {
if (instance == null) {
instance =buildDatabaseInstance(context);
}
return instance;
}
private static SourceDatabase buildDatabaseInstance(Context context) {
return Room.databaseBuilder(context,
SourceDatabase.class,
DB_NAME).build();
}
}
repository:
public class DataBaseRepository {
private static DataBaseRepository dataBaseRepository;
private SourceDao sourceDao;
private LiveData<List<Source>> allSourcestoDb;
private Context context;
public static DataBaseRepository getInstance(Context context) {
if (dataBaseRepository == null) {
dataBaseRepository = new DataBaseRepository(context);
}
return dataBaseRepository;
}
public DataBaseRepository(Context context) {
this.context = context;
SourceDatabase db = SourceDatabase.getInstance(context);
sourceDao = db.sourceDao();
allSourcestoDb = sourceDao.getSources();
}
public void getSourceListTodb(String key) {//отправка данных в LiveData
RestClient restClient = RestClient.getInstance();
restClient.startRetrofit();
restClient.getServerApi().getNews(key).enqueue(new Callback<News>() {
#Override
public void onResponse(Call<News> call, Response<News> response) {
Completable.fromAction(new Action (){
#Override
public void run() throws Exception {
if (response.body() != null) {
List<Source> list = response.body().getSources();
sourceDao.insert(list);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
}
#Override
public void onError(Throwable e) {
}
});
}
#Override
public void onFailure(Call<News> call, Throwable t) {
Log.d("error", "Can't parse data " + t);
}
});
}
public LiveData<List<Source>> getAllSourcestoDb() {
return allSourcestoDb;
}
}
dao:
#Dao
public interface SourceDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(List<Source> sources);
#Query("SELECT * FROM source")
LiveData<List<Source>> getSources();
}
viewModel:
public class SourceViewModel extends AndroidViewModel {
private DataBaseRepository dataBaseRepository;
private LiveData<List<Source>> allSources; //for db
public SourceViewModel(#NonNull Application application) {
super(application);
dataBaseRepository =DataBaseRepository.getInstance(application); //for db
allSources = dataBaseRepository.getAllSourcestoDb();
}
public LiveData<List<Source>> getAllSources() {
return allSources;
}
}
and fragment:
public class SavedDataFragment extends Fragment {
private SourceViewModel sourceViewModel;
private DataBaseRepository dataBaseRepository;
private RecyclerView recyclerView;
private List<Source> sourceList;
private SavedDataAdapter adapter;
public SavedDataFragment() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.saved_data,container,false);
DataSharedPreference sharedPreference = DataSharedPreference.getSPInstance();
String api_key = sharedPreference.loadText(getActivity());
dataBaseRepository = new DataBaseRepository(getActivity());
sourceViewModel = ViewModelProviders.of(this).get(SourceViewModel.class);
recyclerView = view.findViewById(R.id.recyclerViewSavedFragment);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
sourceList = new ArrayList<>();
adapter = new SavedDataAdapter(getActivity(), sourceList);
recyclerView.setAdapter(adapter);
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sourceList);
}
});
dataBaseRepository.getSourceListTodb(api_key);
return view;
}
}
adapter:
public class SavedDataAdapter extends RecyclerView.Adapter<SavedDataAdapter.SourceSavedViewHolder> {
private LayoutInflater inflater;
private List<Source> sources;
public SavedDataAdapter(Context context, List<Source> sources) {
this.sources = sources;
this.inflater = LayoutInflater.from(context);
}
#NonNull
#Override
public SavedDataAdapter.SourceSavedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.saved_item, parent, false);
return new SourceSavedViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final SavedDataAdapter.SourceSavedViewHolder holder, int position) {
final Source source = sources.get(position);
holder.sourceId.setText(source.getId());
holder.sourceName.setText(source.getName());
holder.sourceDescription.setText(source.getDescription());
holder.sourceURL.setText(source.getUrl());
holder.sourceCategory.setText(source.getCategory());
holder.sourceLanguage.setText(source.getLanguage());
holder.sourceCountry.setText(source.getCountry());
}
#Override
public int getItemCount() {
return sources.size();
}
public void setSourceList(List<Source> sources) {
this.sources = sources;
notifyDataSetChanged();
}
public static class SourceSavedViewHolder extends RecyclerView.ViewHolder {
TextView sourceName, sourceId, sourceDescription, sourceURL, sourceCategory, sourceLanguage, sourceCountry;
public SourceSavedViewHolder(View view) {
super(view);
sourceName = view.findViewById(R.id.sourceName);
sourceId = view.findViewById(R.id.sourceIdItem);
sourceDescription = view.findViewById(R.id.sourceDescription);
sourceURL = view.findViewById(R.id.sourceURL);
sourceCategory = view.findViewById(R.id.sourceCategory);
sourceLanguage = view.findViewById(R.id.sourceLanguage);
sourceCountry = view.findViewById(R.id.sourceCountry);
}
}
}

In your Fragment inside onChanged,
you're setting adapter.setSourceList(sourceList) where sourceList is an empty arrayList.
You should instead setSourceList to sources which is the updated list passed as an argument to onChanged method
That is :-
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sources); // sources and not sourceList
}
});
Also there are few more things that should be taken care of.
For ex- in your observe method, you have passed this as first argument which is wrong when using Fragments as it may causes memory leaks. Instead you should pass viewLifeOwner..
More can found on this link Use viewLifecycleOwner as the LifecycleOwner

Try ti change this:
#Query("SELECT * FROM source")
To:
#Query("SELECT * FROM Source")

Related

ViewModel/Repository displaying blank

I'm following the Github Browser app tutorial to build my project without using databinding. The display is just blank only showing the navigation view. I cannot figure out where I am going wrong. I tried to debug and all its showing is that result is null. No errors.
ViewModel.java:
public class ListViewModel extends ViewModel {
private final LiveData<Resource<List<Something>>> repositories;
private int langID;
private int categoryID;
private MutableLiveData<List<Something>> networkInfoObservable = new MutableLiveData<>();
private static String TAG = "SomethingViewModel";
#SuppressWarnings("unchecked")
#Inject
public ListViewModel(ListRepository listRepository) {
repositories = Transformations.switchMap(networkInfoObservable, someList ->
listRepository.loadList(2, 1)); // hard-coding values to test)
}
#VisibleForTesting
public void setListArgs(int langID, int categoryID) {
this.langID = langID;
this.categoryID = categoryID;
}
#VisibleForTesting
public LiveData<Resource<List<Something>>> getList() {
return repositories;
}
}
Repository.java:
#Singleton
public class ListRepository {
private final SomeDao someDao;
private final SomeService someService;
private final AppExecutors appExecutors;
private static String TAG = "LIST_REPOSITORY";
#Inject
ListRepository(AppExecutors appExecutors, SomeDao someDao, SomeService someService) {
this.someDao = someDao;
this.someService = someService;
this.appExecutors = appExecutors;
}
public LiveData<Resource<List<Something>>> loadList(int langId, int categoryID) {
return new NetworkBoundResource<List<Something>, ListWrapper<Something>>(appExecutors) {
#Override
protected void saveCallResult(#NonNull ListWrapper<Something> item) {
for (Something someItem : item.someItems){
someItem.lang_id = langId;
someItem.cat_id = categoryID;
someDao.insertList(someItem);
}
}
#Override
protected boolean shouldFetch(#Nullable List<Something> data) {
return data == null;
}
#NonNull
#Override
protected LiveData<List<Something>> loadFromDb() {
return someDao.getList(langId, categoryID);
}
#NonNull
#Override
protected LiveData<ApiResponse<ListWrapper<Something>>> createCall() {
Log.d(TAG, "Test");
return someService.getList(langId, categoryID);
}
}.asLiveData();
}
}
DAO.java:
#Dao
public interface SomeDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertList(Something someList);
#Query("SELECT * FROM list WHERE langId=:langId AND catId=:catId")
public LiveData<List<Something>> getList(int langId, int catId);
}
APIService.java:
public interface SomeService {
#POST("/getCategoryItem")
LiveData<ApiResponse<ListWrapper<Something>>> getList(#Query("langId") Integer langId,
#Query("catId") Integer category);
}
BaseFragment:
public abstract class BaseFragment<B extends ViewBinding> extends Fragment implements Injectable {
//variables
private ListViewModel listViewModel;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = onCreateBinding(inflater, container, savedInstanceState);
context = this.getActivity().getApplicationContext();
adapter = new ListViewAdapter();
return binding.getRoot();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listViewModel = new ViewModelProvider(this, viewModelFactory).get(ListViewModel.class);
listViewModel.setListArgs(langId, categoryID);
observeListViewModel();
}
private void observeListViewModel() {
listViewModel.getList().observe(getViewLifecycleOwner(), result -> {
if (result != null) {
adapter.setList(result.data);
adapter.setOnItemClickListener(this::itemClicked);
}
});
}
}
I did a little research and figured that I had to implement an inner static class to update the arguments:
public class ListViewModel extends ViewModel {
private final LiveData<Resource<List<Something>>> repositories;
private final MutableLiveData<ListArgs> listArgs;
private static String TAG = "SomethingViewModel";
#SuppressWarnings("unchecked")
#Inject
public ListViewModel(ListRepository listRepository) {
this.listArgs = new MutableLiveData<>();
repositories = Transformations.switchMap(listArgs, listArg ->
listRepository.loadList(listArg.langID, listArg.categoryID));
}
#VisibleForTesting
public void setListArgs(int langID, int categoryID) {
ListArgs update = new ListArgs(langID, categoryID);
if (Objects.equals(listArgs.getValue(), update)) {
return;
}
listArgs.setValue(update);
}
#VisibleForTesting
public LiveData<Resource<List<Something>>> getList() {
return repositories;
}
static class ListArgs {
final int langID;
final int categoryID;
ListArgs(int langID, int categoryID) {
this.langID = langID;
this.categoryID = categoryID;
}
}
}
Your code is implemented inside abstract class that you are not creating the instance so It have not been called

Recycleview not populated with JSON and Retrofit?

I am making a simple app in which the user will able to search for books by its name, with google.com book api. For now, I wish to present a list of books with android in their name. I am doing it with Retrofit2 and RecycleView, but nothing is showing.
MainActivity:
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
KnjigaAdapter knjigaAdapter;
List<KnjigaModel> listaKnjiga;
public static final String BASE_URL = "https://www.googleapis.com/books/";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uzmiKomentare();
}
public void uzmiKomentare() {
Gson gson = new GsonBuilder().serializeNulls().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
KnjigaApi knjigaApi = retrofit.create(KnjigaApi.class);
final Call<KnjigaModel> pozivZaListuKnjiga = knjigaApi.getKnjige("android");
pozivZaListuKnjiga.enqueue(new Callback<KnjigaModel>() {
#Override
public void onResponse(Call<KnjigaModel> call, Response<KnjigaModel> response) {
if (!response.isSuccessful()) {
return;
}
//generateRecycleView(WHAT TO PUT HERE!!!!);
}
#Override
public void onFailure(Call<KnjigaModel> call, Throwable t) {
Log.d("MainActivity:", t.getMessage());
}
});
}
private void generateRecycleView(List<KnjigaModel> knjige) {
listaKnjiga = new ArrayList<>();
recyclerView = findViewById(R.id.recycleview);
knjigaAdapter = new KnjigaAdapter(this, knjige);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(knjigaAdapter);
if (knjigaAdapter.getItemCount() == 0){
Log.i("List is empty: ","YES");
}
else {
Log.i("list is empty: ","No");
}
}
}
api inteface:
public interface KnjigaApi {
#GET("v1/volumes")
Call<KnjigaModel> getKnjige(#Query("q") String knjiga);
}
model class:
public class KnjigaModel {
#SerializedName("title")
#Expose
private String imeKnjige;
#SerializedName("authors")
#Expose
private String imeAutora;
#SerializedName("thumbnail")
#Expose
private String slikaKnjige;
public KnjigaModel(String imeKnjige, String imeAutora,String slikaKnjige) {
this.imeKnjige = imeKnjige;
this.imeAutora = imeAutora;
this.slikaKnjige = slikaKnjige;
}
public String getImeKnjige() {
return imeKnjige;
}
public String getImeAutora() {
return imeAutora;
}
public String getSlikaKnjige() {
return slikaKnjige;
}
}
and my adapter:
public class KnjigaAdapter extends RecyclerView.Adapter<KnjigaAdapter.KomentariViewHolder> {
private List<KnjigaModel> listaKnjiga;
private LayoutInflater inflater;
private Context context;
public KnjigaAdapter(Context context, List<KnjigaModel> listaKnjiga) {
this.listaKnjiga = listaKnjiga;
this.context = context;
}
#Override
public KomentariViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
inflater = LayoutInflater.from(context);
// Inflate the custom layout
View postView = inflater.inflate(R.layout.single_item, parent, false);
// Return a new holder instance
return new KomentariViewHolder(postView);
}
#Override
public void onBindViewHolder(KomentariViewHolder holder, int position) {
KnjigaModel knjige = listaKnjiga.get(position);
holder.naslovKnjige.setText(knjige.getImeKnjige());
holder.imeAutora.setText(knjige.getImeAutora());
Glide.with(context)
.load(knjige.getSlikaKnjige())
.into(holder.slikaKnjige);
}
#Override
public int getItemCount() {
return listaKnjiga.size();
}
public class KomentariViewHolder extends RecyclerView.ViewHolder {
private TextView naslovKnjige;
private TextView imeAutora;
private ImageView slikaKnjige;
public KomentariViewHolder(View itemView) {
super(itemView);
naslovKnjige = itemView.findViewById(R.id.ime_knjige);
imeAutora = itemView.findViewById(R.id.autor_knjige);
slikaKnjige = itemView.findViewById(R.id.sika_korica);
}
}
}
my JSON format:
https://www.googleapis.com/books/v1/volumes?q=android
First of all, I strongly recommend to use RxJava for asynchronous requests, though it's totally optional.
Your Problem:
Your "getKnjige" Method returns only ONE Model, though the endpoint is named volumes (plural), you need to wrap your KnjigaModel in a class like
data class KnjigaResponse(val items: List<KnjigaModel>)
(Kotlin for simplicity, you can also generate a Java class with a single member volumes member that holds a list of KnjigaModel)
In addition, your model is wrong. authors is not a string, but a List of Strings, and "thumbnail" is wrapped in an "imageLinks" Object, and title is wrapped in a volumeInfo Object.
Your retrofit interface then would look like this:
public interface KnjigaApi {
#GET("v1/volumes")
Call<KnjigaResponse> getKnjige(#Query("q") String knjiga);
Request:
final Call<KnjigaResponse> pozivZaListuKnjiga = knjigaApi.getKnjige("android");
pozivZaListuKnjiga.enqueue(new Callback<KnjigaResponse>() {
#Override
public void onResponse(Call<KnjigaResponse> call, Response<KnjigaResponse> response) {
if (!response.isSuccessful()) {
return;
}
generateRecycleView(response.items);
}
#Override
public void onFailure(Call<KnjigaResponse> call, Throwable t) {
Log.d("MainActivity:", t.getMessage());
}
});

Problem with Live Data observers in Room DB Android

I have problem with observing 3 different lists with live data observer for viewing them in one single recycler view.
This is the scenario: In ListsFragment, I have TabLayout with 3 tabs (WANT, PLAYING, PLAYED) and 1 recycler view. On different tab click I load different lists of games from database and I am presenting them with Live Data observer pattern in the recycler view. Until this moment everything works OK.
The problem occurs when I press one game DELETE button, the database is updated but in the recycler view is loading wrong list and not the list of the selected tab (Probably because there are 3 active observers), so the data on the screen is updated with wrong games.
Gif of the behaviour:
https://gfycat.com/showyembarrassedbarasingha
Can anyone please help me, I tried many solutions but nothing helped and I am stuck more than 1 month.
This is the code.
Fragment:
public class ListsFragment extends Fragment implements VisitedGameInterface {
public static final String TAG = ListsFragment.class.getSimpleName();
private static final int WANT = 0;
private static final int PLAYING = 1;
private static final int PLAYED = 2;
private int selectedTab = WANT;
private GameViewModel gameViewModel;
private GameVisitedAdapter gameAdapter;
private TextView noGamesTV;
public ListsFragment() {
// Required empty public constructor
}
public static ListsFragment newInstance(Bundle bundle) {
ListsFragment fragment = new ListsFragment();
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_lists, container, false);
TabLayout listsTabLayout = view.findViewById(R.id.lists_tab_layout);
RecyclerView listsRecyclerView = view.findViewById(R.id.lists_recycler_view);
noGamesTV = view.findViewById(R.id.no_games_tv);
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.want)), true);
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.playing)));
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.played)));
listsTabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
listsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), RecyclerView.VERTICAL, false));
listsRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
gameAdapter = new GameVisitedAdapter(getActivity(), this);
listsRecyclerView.setAdapter(gameAdapter);
gameViewModel = ViewModelProviders.of(this).get(GameViewModel.class);
gameViewModel.getWantedGames().observe(this, new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> wantedGames) {
if (wantedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
}else {
noGamesTV.setText(R.string.no_wanted_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(wantedGames);
gameAdapter.notifyDataSetChanged();
}
});
listsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
if (getActivity() != null && tab.getText() != null) {
if (tab.getText().equals("WANT")) {
selectedTab = tab.getPosition();
gameViewModel.getWantedGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> wantedGames) {
if (wantedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_wanted_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(wantedGames);
gameAdapter.notifyDataSetChanged();
}
});
} else if (tab.getText().equals("PLAYING")) {
selectedTab = tab.getPosition();
gameViewModel.getPlayingGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> playingGames) {
if (playingGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_playing_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(playingGames);
gameAdapter.notifyDataSetChanged();
}
});
} else if (tab.getText().equals("PLAYED")){
selectedTab = tab.getPosition();
gameViewModel.getPlayedGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> playedGames) {
if (playedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_played_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(playedGames);
gameAdapter.notifyDataSetChanged();
}
});
}
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
return view;
}
#Override
public void onGameDelete(final Game game) {
if (selectedTab == WANT){
game.setWanted(false);
showSnackBar(game, WANT);
} if (selectedTab == PLAYING){
game.setPlaying(false);
showSnackBar(game, PLAYING);
} if (selectedTab == PLAYED){
game.setPlayed(false);
showSnackBar(game, PLAYED);
}
gameViewModel.update(game);
gameAdapter.notifyDataSetChanged();
}
#Override
public void onVisitedGameClick(Game game) {
Intent intent = new Intent(getActivity(), GameActivity.class);
intent.putExtra("game", game);
startActivity(intent);
}
private void showSnackBar(final Game snackGame, final int selectedTab){
Snackbar snackbar = Snackbar.make(Objects.requireNonNull(getView()), snackGame.getName() + " deleted", Snackbar.LENGTH_LONG)
.setAction(R.string.undo, new View.OnClickListener() {
#Override
public void onClick(View view) {
if (selectedTab == WANT) snackGame.setWanted(true);
if (selectedTab == PLAYING) snackGame.setPlaying(true);
if (selectedTab == PLAYED) snackGame.setPlayed(true);
gameViewModel.insert(snackGame);
}
});
snackbar.show();
}
}
XML of ListsFragment
<RelativeLayout
android:id="#+id/lists_layout" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".fragment.ListsFragment">
<com.google.android.material.tabs.TabLayout
android:id="#+id/lists_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabSelectedTextColor="#color/button_blue"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/lists_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/lists_tab_layout"/>
<TextView
android:id="#+id/no_games_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
tools:text="No games at this moment"
android:textColor="#color/text_black"
android:visibility="invisible"/>
</RelativeLayout>
The adapter:
public class GameVisitedAdapter extends RecyclerView.Adapter<GameVisitedAdapter.GameVisitedViewHolder> {
private Context context;
private LayoutInflater inflater;
private List<Game> games = new ArrayList<>();
private VisitedGameInterface visitedGameInterface;
public GameVisitedAdapter(Context context, VisitedGameInterface visitedGameInterface){
this.context = context;
inflater = LayoutInflater.from(context);
this.visitedGameInterface = visitedGameInterface;
}
public void setGames(List<Game> games){
this.games = games;
}
#NonNull
#Override
public GameVisitedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_game_visited, parent, false);
return new GameVisitedViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull GameVisitedViewHolder holder, int position) {
Game game = games.get(position);
if (game.getCover() != null) {
Glide
.with(context)
.load(context.getString(R.string.cover_url) + game.getCover().getImage_id() + ".jpg")
.centerCrop()
.placeholder(context.getResources().getDrawable(R.drawable.placeholder))
.into(holder.gameCover);
}
holder.gameTitle.setText(game.getName());
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
String dateString = formatter.format((new Date((long) game.getFirst_release_date() * 1000)));
holder.gameYear.setText(dateString);
}
#Override
public int getItemCount() {
return games.size();
}
class GameVisitedViewHolder extends RecyclerView.ViewHolder {
private ImageView gameCover, gameDeleteButton;
private TextView gameTitle, gameYear;
GameVisitedViewHolder(#NonNull View itemView) {
super(itemView);
gameCover = itemView.findViewById(R.id.game_cover);
gameTitle = itemView.findViewById(R.id.game_name);
gameYear = itemView.findViewById(R.id.game_release_year);
gameDeleteButton = itemView.findViewById(R.id.game_delete_button);
gameDeleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
visitedGameInterface.onGameDelete(games.get(getAdapterPosition()));
}
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
visitedGameInterface.onVisitedGameClick(games.get(getAdapterPosition()));
}
});
}
}
}
The ViewModel
public class GameViewModel extends AndroidViewModel {
private GameRepository gameRepository;
public GameViewModel(#NonNull Application application) {
super(application);
gameRepository = new GameRepository(application);
}
public void insert(Game game){
gameRepository.insert(game);
}
public void update(Game game){
gameRepository.update(game);
}
public void deleteGame(Game game){
gameRepository.delete(game);
}
public void deleteAll(){
gameRepository.deleteAll();
}
public void DeleteVisitedGames(){ gameRepository.deleteVisitedGames();}
public LiveData<List<Game>> getWantedGames(){
return gameRepository.getWantedGames();
}
public LiveData<List<Game>> getPlayingGames(){
return gameRepository.getPlayingGames();
}
public LiveData<List<Game>> getPlayedGames(){
return gameRepository.getPlayedGames();
}
public LiveData<List<Game>> getVisitedGames(){
return gameRepository.getVisitedGames();
}
public LiveData<List<Game>> getAllGamesFromDB(){
return gameRepository.getAllGamesFromDB();
}
}
The Repository:
public class GameRepository {
private GameDao gameDao;
private LiveData<List<Game>> wantedGames;
private LiveData<List<Game>> playingGames;
private LiveData<List<Game>> playedGames;
private LiveData<List<Game>> visitedGames;
private LiveData<List<Game>> allGamesFromDB;
public GameRepository(Application application){
GameDatabase gameDatabase = GameDatabase.getInstance(application);
gameDao = gameDatabase.gameDao();
wantedGames = gameDao.getWantedGames();
playingGames = gameDao.getPlayingGames();
playedGames = gameDao.getPlayedGames();
visitedGames = gameDao.getVisitedGames();
allGamesFromDB = gameDao.getAllGamesFromDB();
}
public LiveData<List<Game>> getWantedGames(){
return wantedGames;
}
public LiveData<List<Game>> getPlayingGames(){
return playingGames;
}
public LiveData<List<Game>> getPlayedGames(){
return playedGames;
}
public LiveData<List<Game>> getVisitedGames(){
return visitedGames;
}
public LiveData<List<Game>> getAllGamesFromDB(){
return allGamesFromDB;
}
public void insert(Game game){
new InsertAsyncTask(gameDao).execute(game);
}
public void update(Game game){
new UpdateAsyncTask(gameDao).execute(game);
}
public void delete(Game game){
new DeleteItemAsyncTask(gameDao).execute(game);
}
public void deleteAll(){
new DeleteAllAsyncTask(gameDao).execute();
}
public void deleteVisitedGames() {new DeleteVisitedAsyncTask(gameDao).execute();}
//-------------INSERT GAME ASYNC TASK
private static class InsertAsyncTask extends AsyncTask<Game, Void, Void> {
private GameDao asyncDao;
public InsertAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.insert(games[0]);
return null;
}
}
//-------------UPDATE GAME ASYNC TASK
private static class UpdateAsyncTask extends AsyncTask<Game, Void, Void> {
private GameDao asyncDao;
public UpdateAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.updateGame(games[0]);
return null;
}
}
//----------DELETE GAME ASYNC TASK
private static class DeleteItemAsyncTask extends AsyncTask <Game, Void, Void> {
private GameDao asyncDao;
public DeleteItemAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.deleteGame(games[0]);
return null;
}
}
//-------------DELETE ALL ASYNC TASK
private static class DeleteAllAsyncTask extends AsyncTask <Void, Void, Void>{
private GameDao asyncDao;
public DeleteAllAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Void... voids) {
asyncDao.deleteAllGames();
return null;
}
}
//-------------DELETE VISITED GAMES ASYNC TASK
private static class DeleteVisitedAsyncTask extends AsyncTask <Void, Void, Void>{
private GameDao asyncDao;
public DeleteVisitedAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Void... voids) {
asyncDao.deleteVisitedGames();
return null;
}
}
}
The DAO:
#Dao
public interface GameDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Game game);
#Delete
void deleteGame(Game game);
#Update
void updateGame(Game game);
#Query("DELETE FROM game_table")
void deleteAllGames();
#Query("SELECT * FROM game_table WHERE isWanted")
LiveData<List<Game>> getWantedGames();
#Query("SELECT * FROM game_table WHERE isPlaying")
LiveData<List<Game>> getPlayingGames();
#Query("SELECT * FROM game_table WHERE isPlayed")
LiveData<List<Game>> getPlayedGames();
#Query("SELECT * FROM game_table WHERE isVisited")
LiveData<List<Game>> getVisitedGames();
#Query("SELECT * FROM game_table")
LiveData<List<Game>> getAllGamesFromDB();
#Query("DELETE FROM game_table WHERE isVisited")
void deleteVisitedGames();
}
Database:
#Database(entities = Game.class, version = 8, exportSchema = false)
#TypeConverters({Converters.class})
public abstract class GameDatabase extends RoomDatabase {
public abstract GameDao gameDao();
private static GameDatabase INSTANCE;
public static GameDatabase getInstance(Context context){
if (INSTANCE == null){
synchronized (GameDatabase.class){
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
GameDatabase.class, "game_database")
.fallbackToDestructiveMigration()
.addCallback(sRoomDatabaseCallback)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback(){
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
};
}
My 2cents: even after you change tabs, your observers are still active, as you are using the same table for everything and I believe (just glance at the code) you are updating your game entry (e.g: isPlayed set to false) thus it send updates to all active observers thus sending update to the recylerview.
With that in mind, in case I am right here, you can:
Make 3 distinct tables (can be in the same database)
use viewpager to have 3 fragments with 3 diffent recyclers (actually you can use viewpager2 with a viewholder instead of making fragments)
have 3 recyclers (and you do a visibility.Gone every time user changes the tab)
In case I am not right, option 2 e 3 still solve your problem. I hope it helps!
p.s: you could also remove and insert observers everytime users changes tab, I find it to be less straightforward though.

RecyclerView is not updated when notifyDataSetChanged is used with MVVM

I am working with MVVM. Main screen shows movie's posters only during debugging (and not during regular run).
The problem is in observation of RecyclerView population. There is Observer in MainActivity. I expect that notifyDataSetChanged method will cause
posters to appear after receiving data from the API, but it doesn't happen.
My cleaned code related to this issue only is available in https://github.com/RayaLevinson/Test
I am missing some important point related to Observer. Please help me! Thank you.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recycler_view_movie);
mMainActivityViewModal = ViewModelProviders.of(this).get(MainActivityViewModel.class);
mMainActivityViewModal.init();
mMainActivityViewModal.getMovies().observe(this, new Observer<List<Movie>>() {
#Override
public void onChanged(#Nullable List<Movie> movies) {
mAdapter.notifyDataSetChanged();
}
});
initRecyclerView();
}
private void initRecyclerView() {
mAdapter = new RecyclerViewAdapter(this, mMainActivityViewModal.getMovies().getValue());
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));
mRecyclerView.setAdapter(mAdapter);
}
MovieRepository.java
public class MovieRepository {
private static final String TAG = "MovieRepository";
private static String mSortBy = "popular";
private static MovieRepository instance;
private List<Movie> movies = new ArrayList<>();
public static MovieRepository getInstance() {
if (instance == null) {
instance = new MovieRepository();
}
return instance;
}
public MutableLiveData<List<Movie>> getMovies() {
setMovies();
MutableLiveData<List<Movie>> data = new MutableLiveData<List<Movie>>();
data.setValue(movies);
return data;
}
private void setMovies() {
Context context = GlobalApplication.getAppContext();
if (NetworkUtils.isNetworkAvailable(context)) {
movies.clear();
new MovieRepository.FetchMoviesTask().execute(mSortBy);
} else {
alertUserAboutNetworkError();
}
}
private void alertUserAboutNetworkError() {
Context context = GlobalApplication.getAppContext();
// Toast.makeText(context, R.string.networkErr, Toast.LENGTH_LONG).show();
}
private class FetchMoviesTask extends AsyncTask<String, Void, List<Movie>> {
#Override
protected List<Movie> doInBackground(String... params) {
if (params.length == 0) {
return null;
}
String sortBy = params[0];
Log.d(TAG, "In doInBackground " + sortBy);
URL moviesRequestUrl = NetworkUtils.buildUrl(sortBy);
try {
String jsonWeatherResponse = NetworkUtils.getResponseFromHttpUrl(moviesRequestUrl);
return MovieJsonUtils.getMoviesDataFromJson(jsonWeatherResponse);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(List<Movie> parsedMoviesData) {
if (parsedMoviesData != null) {
for (Movie movie : parsedMoviesData) {
movies.add(movie);
Log.d(TAG, "In onPostExecute " + " movie was added");
}
}
}
}
}
MainActivityViewModel.java
public class MainActivityViewModel extends ViewModel {
private MutableLiveData<List<Movie>> mMovies;
private MovieRepository mMoviewRepository;
public void init() {
if (mMovies != null) {
return;
}
mMoviewRepository = MovieRepository.getInstance();
mMovies = mMoviewRepository.getMovies();
}
public LiveData<List<Movie>> getMovies() {
return mMovies;
}
}
RecyclerViewAdapter.java
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private static final String TAG = "RecyclerViewAdapter";
private final Context mContext;
private List<Movie> mMovies;
public RecyclerViewAdapter(Context mContext, List<Movie> movies) {
this.mMovies = movies;
this.mContext = mContext;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_list_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder called");
Picasso.get()
.load(mMovies.get(holder.getAdapterPosition()).getPosterPath())
.placeholder(R.mipmap.ic_launcher)
.into(holder.image);
}
#Override
public int getItemCount() {
return mMovies.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
final ImageView image;
final LinearLayout parentLayout;
private ViewHolder(#NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.image);
parentLayout = itemView.findViewById(R.id.parent_layout);
}
}
public void update(List<Movie> movies) {
mMovies.clear();
mMovies.addAll(movies);
notifyDataSetChanged();
}
}
Your MovieRepository#getMovies() executes the Livedata.setValue() before the AsyncTask finishes. You can see that in your debug output.
What you have to do is to call postValue() (cause your on not on the mainthread) in your onPostExecute() method. Then you have to call mAdapter.update() from the onChanged() method.
Also I would recommend to refactor your ViewModel a little bit. Remove the call to the repository from your init() method and create a new method that only calls the load function from the repo. So if you later on would like to support things like endless scrolling, this will help you a lot.
Just a matter of opinion, but i like to create my observables inside my ViewModel and not in the Repository and pass it along as parameter. Thats how it could look like:
Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
...
viewModel = ViewModelProviders.of(this).get(YOUR_VIEW_MODEL.class);
viewModel.init();
viewModel.getItemsObservable().observe(this, new Observer<List<Item>>() {
#Override
public void onChanged(#Nullable List<Item> items) {
// Add/replace your existing adapter
adapter.add/replaceItems(items);
// For better performance when adding/updating elements you should call notifyItemRangeInserted()/notifyItemRangeChanged(). For replacing the whole dataset notifyDataSetChanged() is fine
adapter.notifyDataSetChanged();
// Normally i would put those calls inside the adapter and make appropriate methods but for demonstration.
}
});
initRecyclerView();
viewModel.loadItems()
}
ViewModel
public void init(){
repository = Repository.getInstance();
}
public void loadItems(){
repository.loadItems(getItemsObservable());
}
public LiveData<List<Item>> getItemsObservable() {
if (items == null) {
items = new MutableLiveData<>();
}
return items;
}
Repository
public void loadItems(LiveData<List<Item>> liveData){
List<Item> data = remote.getDataAsync(); // get your data asynchronously
liveData.postValue(data); // call this after you got your data, in your case inside the onPostExecute() method
}

Android Retrofit 2.1.0 Response.body() is null, status code is 404

I am trying to make a call to this api and am having difficulty as the response.body() is returning null.
http://demo.museum.vebrary.vn/api/stuff/getall
I want to get stuff name of list and show to my recyclerview.
My model:
public class SOAnswersResponse {
#SerializedName("StuffModels")
#Expose
private List<StuffModel> stuffModels = null;
public List<StuffModel> getStuffModels() {
return stuffModels;
}
public void setStuffModels(List<StuffModel> stuffModels) {
this.stuffModels = stuffModels;
}
and
public class StuffModel {
#SerializedName("STUFFID")
#Expose
private Integer sTUFFID;
#SerializedName("STUFFCODE")
#Expose
private String sTUFFCODE;
#SerializedName("STUFFNAME")
#Expose
private String sTUFFNAME;
#SerializedName("STUFFNOTE")
#Expose
private String sTUFFNOTE;
#SerializedName("STUFFORDER")
#Expose
private Integer sTUFFORDER;
#SerializedName("CUSTOMERID")
#Expose
private String cUSTOMERID;
#SerializedName("EXHIBITS")
#Expose
private List<Object> eXHIBITS = null;
Json response
{
"StuffModels":[
{
"STUFFID":2,
"STUFFCODE":"Gi",
"STUFFNAME":"Giấy",
"STUFFNOTE":"",
"STUFFORDER":2,
"CUSTOMERID":"CAMAU",
"EXHIBITS":[
]
},
ApiUtils Class
public class ApiUtils {
private ApiUtils() {
}
public static final String BASE_URL = "http://demo.museum.vebrary.vn/api/";
public static SOService getSOService() {
return RetrofitClient.getClient(BASE_URL).create(SOService.class);
}
}
Service interface
public interface SOService {
#GET("/stuff/getall")
Call<SOAnswersResponse> getAnswers();
}
RetrofitClient Class
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
My RecyclerView adapter
public class CategogyNameRecyclerViewAdapter extends RecyclerView.Adapter<CategogyNameRecyclerViewAdapter.ViewHolder> {
private List<StuffModel> mItems;
private Context mContext;
private PostItemListener mItemListener;
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView titleTv;
PostItemListener mItemListener;
public ViewHolder(View itemView, PostItemListener postItemListener) {
super(itemView);
titleTv = itemView.findViewById(R.id.tvListMenuCategogy);
this.mItemListener = postItemListener;
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
StuffModel item = getItem(getAdapterPosition());
this.mItemListener.onPostClick(item.getSTUFFID());
notifyDataSetChanged();
}
}
public CategogyNameRecyclerViewAdapter(Context context, List<StuffModel> posts, PostItemListener itemListener) {
mItems = posts;
mContext = context;
mItemListener = itemListener;
}
#Override
public CategogyNameRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View postView = inflater.inflate(R.layout.item_list_text, parent, false);
ViewHolder viewHolder = new ViewHolder(postView, this.mItemListener);
return viewHolder;
}
#Override
public void onBindViewHolder(CategogyNameRecyclerViewAdapter.ViewHolder holder, int position) {
StuffModel item = mItems.get(position);
TextView textView = holder.titleTv;
textView.setText(item.getSTUFFNAME());
}
#Override
public int getItemCount() {
return mItems.size();
}
public void updateAnswers(List<StuffModel> items) {
mItems = items;
notifyDataSetChanged();
}
private StuffModel getItem(int adapterPosition) {
return mItems.get(adapterPosition);
}
public interface PostItemListener {
void onPostClick(long id);
}
}
And my main activity
public class Testttt extends AppCompatActivity {
private CategogyNameRecyclerViewAdapter mAdapter;
private RecyclerView mRecyclerView;
private SOService mService;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView(R.layout.test );
mService = ApiUtils.getSOService();
mRecyclerView = (RecyclerView) findViewById(R.id.rcvCategogyNameMenuTest);
mAdapter = new CategogyNameRecyclerViewAdapter(this, new ArrayList<StuffModel>(0), new CategogyNameRecyclerViewAdapter.PostItemListener() {
#Override
public void onPostClick(long id) {
Toast.makeText(Testttt.this, "Post id is" + id, Toast.LENGTH_SHORT).show();
}
});
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
mRecyclerView.addItemDecoration(itemDecoration);
loadAnswers();
}
public void loadAnswers() {
mService.getAnswers().enqueue(new Callback<SOAnswersResponse>() {
#Override
public void onResponse(Call<SOAnswersResponse> call, Response<SOAnswersResponse> response) {
Toast.makeText(Testttt.this, "333333333333333333"+response.body(), Toast.LENGTH_SHORT).show();
if(response.isSuccessful()) {
mAdapter.updateAnswers(response.body().getStuffModels());
Log.d("AnswersPresenter", "posts loaded from API");
}else {
int statusCode = response.code();
}
}
#Override
public void onFailure(Call<SOAnswersResponse> call, Throwable t) {
showErrorMessage();
Log.d("AnswersPresenter", "error loading from API");
}
});
}
public void showErrorMessage() {
Toast.makeText(this, "Error loading posts", Toast.LENGTH_SHORT).show();
}
}
The first thing that came in my mind:
Your
public static final String BASE_URL = "http://demo.museum.vebrary.vn/api/";
has a "/" at the the end and your
#GET("/stuff/getall")
Call<SOAnswersResponse> getAnswers();
starts with a "/". So there is a double backslash in the url that might leads to the 404 code. Does this solve the problem?
When i call your URL i receive XML. Maybe the API is not configured correctly?
Change your Service interface
public interface SOService {
#GET("stuff/getall")
Call<SOAnswersResponse> getAnswers();
}
it occurred because you have use start with backslash it already added in your base url

Categories

Resources