Recycleview not populated with JSON and Retrofit? - android

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());
}
});

Related

Fetching data with retrofit2 and saving in room

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")

How to call Json using Retrofit

I have JSON as below:
{
"Search": [
{
"Title": "Iron Man",
"Year": "2008"
},
{
"Title": "Iron Man2",
"Year": "20010"
},
{
"Title": "Iron Man3",
"Year": "2013"
}
]
}
Then I've created APIservice as below:
public interface MyApi {
#GET("/movies")
Call<ArrayList<MovieSearch>> getartistdata();
}
My data classes as per below
public class MovieSearch {
#SerializedName("Search")
public List<Search> Search =null;
}
public class Search {
#SerializedName("Title")
public String Title="";
#SerializedName("Year")
public String Year="";
#SerializedName("Poster")
public String Poster="";
public Search() {
}
public Search(String Title, String Year, String Poster) {
this.Title = Title;
this.Year = Year;
}
}
Now I'm trying to implement viewmodel class as below
public class MyListViewModel extends ViewModel {
public String Title = "";
public String Year= "";
public String Poster = "";
public MutableLiveData<ArrayList<MyListViewModel>> mutableLiveData = new MutableLiveData<>();
private ArrayList<MyListViewModel> arrayList;
private ArrayList<MovieSearch> myList;
public String getImageurl() {
return Poster;
}
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public MyListViewModel() {
}
public MyListViewModel(MovieSearch myList) {
this.Title = myList.Title;
this.Poster = myList.Poster;
this.Year= myList.Year;
}
public MutableLiveData<ArrayList<MyListViewModel>> getMutableLiveData() {
arrayList = new ArrayList<>();
MyApi api = MyClient.getInstance().getMyApi();
Call<ArrayList<MovieSearch>> call = api.getartistdata();
call.enqueue(new Callback<ArrayList<MovieSearch>>() {
#Override
public void onResponse(Call<ArrayList<MovieSearch>> call, Response<ArrayList<MovieSearch>> response) {
myList = new ArrayList<>();
myList = response.body();
for (int i = 0; i < myList.size(); i++) {
MovieSearch myk = myList.get(i);
MyListViewModel myListViewModel = new MyListViewModel(myk);
arrayList.add(myListViewModel);
mutableLiveData.setValue(arrayList);
}
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
}
});
return mutableLiveData;
}
}
But I'm getting "Cannot resolve symbol" error on the following:
public MyListViewModel(MovieSearch myList) {
this.Title = myList.Title;
this.Poster = myList.Poster;
this.Year= myList.Year;
}
I'm trying to get the data from JSON and bind it to view holder. But I couldn't figure it out how to call it. Someone please help me to fix it.
You are pretty close, you just have to make few changes in service class and viewmodel to make this work. Make below changes
MyApi interface: -> becoz your json is not arraylist but an object of MovieSearch so make change accordingly inside that the arraylist of search is there.
public interface MyApi {
#GET("/movies")
Call<MovieSearch> getartistdata();
}
MyListViewModel class:
public class MyListViewModel extends ViewModel {
public MutableLiveData<ArrayList<Search>> mutableLiveData = new MutableLiveData<>();
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public MyListViewModel() {
//do something else, your view model is not POJO it's the handler not storage.
}
public MutableLiveData<ArrayList<Search>> getSearchResults() {
MutableLiveData<ArrayList<Search>> localData = new MutableLiveData<>();
MyApi api = MyClient.getInstance().getMyApi();
Call<MovieSearch> call = api.getartistdata();
call.enqueue(new Callback<MovieSearch>() {
#Override
public void onResponse(Call<MovieSearch> call, Response<MovieSearch> response) {
List<Search> myList = response.body().Search;
mutableLiveData.setValue(myList);
localData.setValue(myList);
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
//handle the error
}
});
return localData;
}
}
Call the above view model by creating an object of view model and inside some function. this part usually goes inside some UI class activity or fragment:
//listening for change in live data
// this would be in ui layer
myListViewModelObject.getSearchResults().observe(this, new Observer<ArrayList<Search>>() {
#Override
public void onChanged(#Nullable ArrayList<Search> obj) {
// handle changes here, to use the data write code below
}
});
Advice: Don't use your viewmodel to store the data it's not what is made for. It is for handling the data and actions not the keeper of data. Managing the data and business logic in viewmodel can cause many problems. Always if possible break things into smaller parts like, get retrofit object from some other class or mathod don't put it in the calling method itself this will code duplication and may impact the performance also.
Note: I haven't tested the code just made the best possible changes and it should run. Please add the missing parts if i have removed any.
Here I noticed that you are keeping an array of ViewModels. The main thing is ViewModels are not meant to be used like that.
I think its better if you read some documentation and sample implementations of ViewModels to have a better understanding.
Coming to your implementation, everything looks fine but one thing that is converting your list of MovieSearch into a list of MyListViewModel.
In ViewModel
public class MyListViewModel extends ViewModel {
private MutableLiveData<MovieSearch> mutableLiveData = new MutableLiveData<>();
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public LiveData<MovieSearch> getMutableLiveData() {
loadData();
return mutableLiveData;
}
private void loadData(){
MyApi api = MyClient.getInstance().getMyApi();
Call<MovieSearch> call = api.getartistdata();
call.enqueue(new Callback<MovieSearch>() {
#Override
public void onResponse(Call<MovieSearch> call, Response<MovieSearch> response) {
MovieSearch movieSearch = response.body();
mutableLiveData.setValue(myList);
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
}
});
}
}
In activity or fragment, you can access the liveData through getMutableLiveData() and set the List<MovieSearch> to the adapter.
One more thing, given your response JSON, your API should like below
public interface MyApi {
#GET("/movies")
Call<MovieSearch> getartistdata();
}
MovieSearch is not in a list
Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<Search> arrayList = new ArrayList<>;
private LayoutInflater layoutInflater;
public void submitList(ArrayList<Search> searchList){
this.arrayList = searchList;
notifyDataSetChanged();
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if (layoutInflater==null){
layoutInflater=LayoutInflater.from(parent.getContext());
}
MyListBinding myListBinding= DataBindingUtil.inflate(layoutInflater, R.layout.mylist,parent,false);
return new ViewHolder(myListBinding);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
Search search =arrayList.get(position);
holder.bind(search);
}
#Override
public int getItemCount() {
return arrayList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
private MyListBinding myListBinding;
public ViewHolder(#NonNull MyListBinding myListBinding) {
super(myListBinding.getRoot());
this.myListBinding=myListBinding;
}
public void bind(Search myli){
this.myListBinding.setMylistmodel(myli);
myListBinding.executePendingBindings();
}
public MyListBinding getMyListBinding(){
return myListBinding;
}
}
}
Activity
adapter = MyAdapter();
recyclerview=(RecyclerView)findViewById(R.id.recyclerView);
recyclerview.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerview.setAdapter(adapter);
myListViewModel = ViewModelProviders.of(this).get(MyListViewModel.class);
myListViewModel.getSearchResults().observe(this, new Observer<MovieSearch>() {
#Override
public void onChanged(#Nullable MovieSearch movieSearch) {
// handle changes here, to use the data write code below
adapter.submitList(movieSearch.Search);
}
});
And one small thing, please don't start variable names with capital letters. It is better to follow naming conventions from the start.

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

iam using Retrofit library to fetch data from Database to recyclerview

am trying to Fetch the movies data from Mysql DB and show it to Recycler view
but when i run the app nothing shows
here is code i am using Retrofite Library
but i can't parse the Data to the Recycler view
i've made Adapter and Model Class normally like the Json
MainActivity.class
public class MainActivity extends AppCompatActivity {
private static final String url="http://192.168.1.109/stu/";
RecyclerView recyclerViewMovies;
List<MovieListsModels> movies;
MoviesAdapter adapter;
TextView Errortxt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Errortxt = (TextView)findViewById(R.id.txterror);
recyclerViewMovies = (RecyclerView)findViewById(R.id.recyclerview);
recyclerViewMovies.setHasFixedSize(true);
recyclerViewMovies.setLayoutManager(new LinearLayoutManager(this));
movies = new ArrayList<>();
loadDatafromServer();
}
private void loadDatafromServer() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Call<MovieListsModels> call = api.ShowMoviesData();
call.enqueue(new Callback<MovieListsModels>() {
#Override
public void onResponse(Call<MovieListsModels> call, Response<MovieListsModels> response) {
try {
MovieListsModels movie = response.body();
adapter = new MoviesAdapter(MainActivity.this, (List<MovieListsModels>) movie);
recyclerViewMovies.setAdapter(adapter);
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<MovieListsModels> call, Throwable t) {
Errortxt.setText(t.getMessage().toString());
}
});
}
this is the interface of the methods
Api.class Interface
public interface Api {
#GET("config.php")
Call<MovieListsModels> ShowMoviesData();
}
MovieLists.class
public class MovieListsModels {
public MovieListsModels() {
}
int id;
String movie_name;
String movie_image;
String movie_genre;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMovie_name() {
return movie_name;
}
public void setMovie_name(String movie_name) {
this.movie_name = movie_name;
}
public String getMovie_image() {
return movie_image;
}
public void setMovie_image(String movie_image) {
this.movie_image = movie_image;
}
public String getMovie_genre() {
return movie_genre;
}
public void setMovie_genre(String movie_genre) {
this.movie_genre = movie_genre;
}
public MovieListsModels(int id, String movie_name, String movie_image, String movie_genre) {
this.id = id;
this.movie_name = movie_name;
this.movie_image = movie_image;
this.movie_genre = movie_genre;
}
}
MovieAdapter.class
public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MovieHolderView> {
private Context mContext;
private List<MovieListsModels> MovieList = new ArrayList<>();
public MoviesAdapter(Context mContext, List<MovieListsModels> movieList) {
this.mContext = mContext;
MovieList = movieList;
}
#NonNull
#Override
public MovieHolderView onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item,parent,false);
MovieHolderView holder = new MovieHolderView(view);
return holder;
}
#Override
public void onBindViewHolder(#NonNull MovieHolderView holder, int position) {
MovieListsModels list = MovieList.get(position);
holder.txtName.setText(list.getMovie_name());
holder.txtGenre.setText(list.getMovie_genre());
Picasso.get()
.load(list.getMovie_image())
.into(holder.imgMovie);
}
#Override
public int getItemCount() {
return MovieList.size();
}
public class MovieHolderView extends RecyclerView.ViewHolder {
TextView txtName,txtGenre;
ImageView imgMovie;
public MovieHolderView(View itemView) {
super(itemView);
txtName =(TextView)itemView.findViewById(R.id.movieName);
txtGenre =(TextView)itemView.findViewById(R.id.movieGenre);
imgMovie =(ImageView)itemView.findViewById(R.id.movieImg);
}
}
}
If you receive a list of movies is better because you expect a list, I suppose
public void onResponse(Call<MovieListsModels> call, Response<MovieListsModels> response) {
try {
List<MovieListsModels> movie = response.body();
adapter = new MoviesAdapter(MainActivity.this, movies);
And I believe that not executing the notifyDataSetChanged, you can added like that:
private Context mContext;
private List<MovieListsModels> MovieList = new ArrayList<>();
public MoviesAdapter(Context mContext, List<MovieListsModels> movieList) {
this.mContext = mContext;
MovieList = movieList;
notifiyDataSetChanged();
If you are having json response of the form {..}, you are having an object response and you should expect an object as you have done i.e, Call<YourObject>
If you are having json response of the form [..], you are having an array response and you should expect an array i.e, Call<List<YourObject>>
In your case, i hope its an array(second case), So make changes as per the above answer done by #Guillodacosta
First don't forget to add the internet permission in your manifest file
<uses-permission android:name="android.permission.INTERNET" />
Second try this
Picasso.with(mContext).load(list.getMovie_image()).into(holder.imgMovie);

Categories

Resources