LiveData with ModelView not updating adapter - java

I am learning new components and try to use LiveData with DataBinding and ModelView. I have a problem with showing items on recyclerview (updating UI). Here is my ModelView:
 
public class PlacesViewModel extends ViewModel {
private MutableLiveData<List<Place>> placeList;
private Repository repository;
public PlacesViewModel() {
repository = Repository.getInstance();
}
public LiveData<List<Place>> getPlaceList() {
if (placeList == null) {
placeList = new MutableLiveData<List<Place>>();
getPlaces();
}
return placeList;
}
private void getPlaces() {
repository.getLocationsFromBackend(new DataSource.GetLocationsCallback() {
#Override
public void onSuccess(MutableLiveData<List<Place>> body) {
if (body != null) {
placeList = body;
} else {
placeList = new MutableLiveData<>();
}
}
#Override
public void onError(int code) {
}
#Override
public void onUnknownError() {
}
#Override
public void onNoInternet() {
}
#Override
public void onNoServer() {
}
#Override
public void onAlreadyPairedError() {
}
#Override
public void onNoCustomerError() {
}
#Override
public void onLoadIndicator(boolean active) {
}
#Override
public void reAuthenticate() {
}
});
}
The problem comes, when I launch the app, screen is "empty". But, when I am in debug mode and in line getPlaceList(), I can switch to background thread to execute api call using retrofit. Only than it works...
Here is my Repository singleton with method:
public class Repository implements DataSource {
private static Repository INSTANCE;
private ApiService service = new RetrofitFactory().getService();
private static final int RESPONSE_OK = 0;
private final MutableLiveData<List<Place>> data = new MutableLiveData<>();
public synchronized static Repository getInstance() {
if (INSTANCE==null) {
INSTANCE=new Repository();
}
return(INSTANCE);
}
private Repository() {
}
private <T extends BaseResponse> void handleActualResult(BaseLoadCallback<T> dataSource,
MutableLiveData<List<Place>> actualResult) {
final int resultCode = 0;
switch (resultCode) {
case RESPONSE_OK:
dataSource.onSuccess(actualResult);
break;
default:
dataSource.onError(resultCode);
break;
}
}
#Override
public void getLocationsFromBackend(GetLocationsCallback presener) {
final SecurityRequest request = new SecurityRequest();
service.getPlaces(request).enqueue(new Callback<GetPlacesResponse>() {
#Override
public void onResponse(#NonNull Call<GetPlacesResponse> call, #NonNull Response<GetPlacesResponse> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
data.postValue(response.body().getLokali());
handleActualResult(presener, data);
}
}
}
#Override
public void onFailure(#NonNull Call<GetPlacesResponse> call, #NonNull Throwable t) {
Log.d("Error: ", t.getMessage());
}
});
}
#Override
public List<Place> getLocations() {
return null;
}
}
It goes to onResposne and than back to getPlaceList() and returns filled list with objects. Here is how I make an instance of viewmodel and observe LiveData object:
public class LocationFragment extends Fragment {
private PlacesViewModel placesViewModel;
private RecyclerView recyclerView;
private PlaceCustomAdapter customAdapter;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recyclerViewId);
placesViewModel = ViewModelProviders.of(LocationFragment.this).get(PlacesViewModel.class);
customAdapter = new PlaceCustomAdapter(getLayoutInflater());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setHasFixedSize(false);
placesViewModel.getPlaceList().observe(this, places -> {
customAdapter.setList(places);
recyclerView.setAdapter(customAdapter);
});
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return(inflater.inflate(R.layout.activity_main, container, false));
}
Here is also my adapter:
public class PlaceCustomAdapter extends RecyclerView.Adapter<PlaceCustomAdapter.CustomLocationViewHolder> {
private LayoutInflater layoutInflater;
private List<Place> placeList;
public PlaceCustomAdapter(LayoutInflater layoutInflater) {
this.layoutInflater = layoutInflater;
}
#NonNull
#Override
public PlaceCustomAdapter.CustomLocationViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.single_item_row_recycler_view, parent, false);
return new PlaceCustomAdapter.CustomLocationViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull PlaceCustomAdapter.CustomLocationViewHolder holder, int position) {
Place p = placeList.get(position);
holder.bind(p);
}
void setList(List<Place> placeList) {
this.placeList = placeList;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return(placeList == null ? 0 : placeList.size());
}
public class CustomLocationViewHolder extends RecyclerView.ViewHolder {
private final ViewDataBinding binding;
public CustomLocationViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.model, obj);
binding.executePendingBindings();
}
}
So, it does not crash, only recycler is empty...If you need any extra explanation, just ask.
I would appreciate any help. Thanx

I just figured it out what was wrong. I was calling a method handleActualResult(presener, data);
in wrong place. Instead in onSuccess I put it out to handle response...
#Override
public void getLocationsFromBackend(GetLocationsCallback presener) {
final SecurityRequest request = new SecurityRequest();
service.getPlaces(request).enqueue(new Callback<GetPlacesResponse>() {
#Override
public void onResponse(#NonNull Call<GetPlacesResponse> call, #NonNull Response<GetPlacesResponse> response) {
if (response.isSuccessful()) {
if (response != null) {
data.postValue(response.body().getLokali());
} else {
data = new MutableLiveData<>();
}
}
}
#Override
public void onFailure(#NonNull Call<GetPlacesResponse> call, #NonNull Throwable t) {
Log.d("Error: ", t.getMessage());
}
});
handleActualResult(presener, data);
}
If anyone counter the same problem...Enjoy!

Related

Unable to get data from Retrofit when used in for loop

I'm using MVVM and I'm trying to loop over a list of integers that has some IDs for movies genres, I'm using retrofit and I have a function that should return a list of movies based on the id, but it's working for some reason.
I tried to pass the id to the function and it worked, however when I tried to loop over a list of ids to get different results and put them in a RecyclerView it didn't work
MainActivity:
public class MainActivity extends AppCompatActivity {
RecyclerView mMainRecycler;
MoviesViewModel moviesViewModel;
MainRecyclerAdapter mainRecyclerAdapter;
Context context = this;
List<String> genresNames = new ArrayList<>();
List<Integer> genresIds = new ArrayList<>();
List<Movies.Result> listResults = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMainRecycler = findViewById(R.id.main_recycler);
mMainRecycler.setLayoutManager(new LinearLayoutManager(this));
moviesViewModel = new ViewModelProvider(this).get(MoviesViewModel.class);
moviesViewModel.getMoviesGenres();
moviesViewModel.mutableMoviesGenres.observe(this, new Observer<List<MoviesGenres.GenresBean>>() {
#Override
public void onChanged(List<MoviesGenres.GenresBean> genresBeans) {
for (int i = 0; i < genresBeans.size(); i++) {
genresIds.add(genresBeans.get(i).getId());
genresNames.add(genresBeans.get(i).getName());
}
mainRecyclerAdapter.addNames(genresNames);
}
});
for(int i = 0; i < genresIds.size(); i++) {
moviesViewModel.getMovieWithGenre(genresIds.get(i));
}
moviesViewModel.mutableMoviesWithGenre.observe(this, new Observer<List<Movies.Result>>() {
#Override
public void onChanged(List<Movies.Result> results) {
listResults.addAll(results);
mainRecyclerAdapter.addResults(listResults);
}
});
mMainRecycler.setLayoutManager(new LinearLayoutManager(this));
mainRecyclerAdapter = new MainRecyclerAdapter(context, genresNames, listResults);
mMainRecycler.setAdapter(mainRecyclerAdapter);
}
}
ViewModel:
public class MoviesViewModel extends ViewModel {
public MutableLiveData<List<Movies.Result>> mutablePopularMovies = new MutableLiveData<>();
public void getPopular(){
MoviesClient.getInstance().getPopular().enqueue(new Callback<Movies>() {
#Override
public void onResponse(#NonNull Call<Movies> call, #NonNull Response<Movies> response) {
if(response.isSuccessful() && response.body() != null) {
mutablePopularMovies.setValue(response.body().getResults());
}
}
#Override
public void onFailure(#NonNull Call<Movies> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
}
public MutableLiveData<List<Movies.Result>> mutableMoviesWithGenre = new MutableLiveData<>();
public void getMovieWithGenre(int i){
MoviesClient.getInstance().getMoviesWithGenre(i).enqueue(new Callback<Movies>() {
#Override
public void onResponse(#NonNull Call<Movies> call, #NonNull Response<Movies> response) {
if(response.isSuccessful() && response.body() != null) {
mutableMoviesWithGenre.setValue(response.body().getResults());
}
}
#Override
public void onFailure(#NonNull Call<Movies> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
}
public MutableLiveData<List<MoviesGenres.GenresBean>> mutableMoviesGenres = new MutableLiveData<>();
public void getMoviesGenres() {
MoviesClient.getInstance().getMoviesByGenre().enqueue(new Callback<MoviesGenres>() {
#Override
public void onResponse(#NonNull Call<MoviesGenres> call, #NonNull Response<MoviesGenres> response) {
if(response.isSuccessful() && response.body() != null) {
mutableMoviesGenres.setValue(response.body().getGenres());
}
}
#Override
public void onFailure(#NonNull Call<MoviesGenres> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
}
}
MainRecyclerViewAdapter:
public class MainRecyclerAdapter extends RecyclerView.Adapter<MainRecyclerAdapter.MainViewHolder> {
private Context mContext;
private List<Movies.Result> moviesResults;
private List<String> moviesNames;
public MainRecyclerAdapter(Context context, List<String> moviesNames, List<Movies.Result> moviesResults) {
this.mContext = context;
this.moviesNames = moviesNames;
this.moviesResults = moviesResults;
}
#NonNull
#Override
public MainRecyclerAdapter.MainViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new MainViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.category_rv_item, parent, false));
}
#Override
public void onBindViewHolder(#NonNull final MainRecyclerAdapter.MainViewHolder holder, int position) {
holder.genresTv.setText(moviesNames.get(position));
setCatItemRecycler(holder.mRecycler, moviesResults);
}
#Override
public int getItemCount() {
return moviesNames.size();
}
public void addNames(List<String> moviesGenres) {
moviesNames.addAll(moviesGenres);
notifyDataSetChanged();
}
public void addResults(List<Movies.Result> listResults) {
moviesResults.addAll(listResults);
notifyDataSetChanged();
}
public class MainViewHolder extends RecyclerView.ViewHolder {
TextView genresTv;
RecyclerView mRecycler;
public MainViewHolder(#NonNull View itemView) {
super(itemView);
genresTv = itemView.findViewById(R.id.cat_title);
mRecycler = itemView.findViewById(R.id.item_recycler);
}
}
private void setCatItemRecycler(RecyclerView recyclerView, List<Movies.Result> moviesResults){
MovieAdapter movieAdapter = new MovieAdapter(mContext, moviesResults);
recyclerView.setLayoutManager(new LinearLayoutManager(mContext, RecyclerView.HORIZONTAL, false));
recyclerView.setAdapter(movieAdapter);
}
}
Chile (inner) RecyclerView:
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MovieViewHolder> {
private List<Movies.Result> moviesResult;
private Context mContext;
public MovieAdapter(Context context, List<Movies.Result> moviesResult) {
this.moviesResult = moviesResult;
this.mContext = context;
}
#NonNull
#Override
public MovieAdapter.MovieViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new MovieViewHolder(LayoutInflater.from(mContext).inflate(R.layout.movie_item, parent, false));
}
#Override
public void onBindViewHolder(#NonNull MovieAdapter.MovieViewHolder holder, int position) {
String imageUrl = MoviesClient.IMAGE_URL + moviesResult.get(position).getPosterPath();
Glide.with(holder.mPosterImage.getContext()).load(imageUrl).into(holder.mPosterImage);
}
#Override
public int getItemCount() {
return moviesResult.size();
}
public class MovieViewHolder extends RecyclerView.ViewHolder {
ImageView mPosterImage;
public MovieViewHolder(#NonNull View itemView) {
super(itemView);
mPosterImage = itemView.findViewById(R.id.poster_image);
}
}
}
first of all you didn't share the ViewModel class, I thing you may have a problem with the observers, because your listResults will only be updated after the Retrofit returns the result, so when you call mMainRecycler.setAdapter(mainRecyclerAdapter) you are doing it with your empty list.
Another thing that may be causing this is because your data does not know it has returned, so when you use listResults.addAll(results), make sure that inside the adapter you call .notifyDataSetChanged()
A function like this should do the trick (inside the adapter):
public void updateGenres(List<Genres> genres){
listResults.addAll(genres);
this.notifyDataSetChanged();
}

How to fix 'java.lang.ClassCastException: Activity must implement fragment's callbacks'

I am a beginner who am writing a code that consists of two activities. Both have their own Fragments. On the first activity (in its fragment), the user input some fields. The result will be displayed on the second activity (in its list fragment).
I have tried to get state manually using bundle. And now, I am using callback to do that. However, I got an error message java.lang.ClassCastException: Activity must implement fragment's callbacks.
The first activity is:
public class AssetRegistrationInfoPage extends Page {
public static final String ASSET_TYPE_NAME_KEY = "assetTypeName";
public static final String ASSET_ID_DATA_KEY = "assetID";
public static final String PROJECT_CODE_DATA_KEY = "projectCode";
public static final String REMARK_DATA_KEY = "remark";
public AssetRegistrationInfoPage(ModelCallbacks callbacks, String title) {
super(callbacks, title);
}
#Override
public Fragment createFragment() {
return AssetRegistrationInfoFragment.create(getKey());
}
#Override
public void getReviewItems(ArrayList<ReviewItem> dest) {
dest.add(new ReviewItem("Nama Asset", mData.getString(ASSET_TYPE_NAME_KEY), getKey(), 0, null));
dest.add(new ReviewItem("Kode Asset", mData. getString(ASSET_ID_DATA_KEY), getKey(), 0, null));
dest.add(new ReviewItem("Kode Project", mData.getString(PROJECT_CODE_DATA_KEY), getKey(),0,null));
dest.add(new ReviewItem("Kondisi Asset", mData.getString(REMARK_DATA_KEY), getKey(), 0, null));
}
#Override
public boolean isCompleted() {
return (!TextUtils.isEmpty(mData.getString(ASSET_TYPE_NAME_KEY)) &&
!TextUtils.isEmpty(mData.getString(ASSET_ID_DATA_KEY)) &&
!TextUtils.isEmpty(mData.getString(PROJECT_CODE_DATA_KEY)) &&
!TextUtils.isEmpty(mData.getString(REMARK_DATA_KEY)));
}
}
The first fragment:
public class AssetRegistrationInfoFragment extends Fragment {
private static final String ARG_KEY = "key";
private PageFragmentCallbacks mCallbacks;
private String mKey;
private AssetRegistrationInfoPage mPage;
private TextView mAssetID;
private AutoCompleteTextView mAssetTypeName, mProjectCode;
private TextView mRemark;
private Button mTakePicture;
static final int REQUEST_PICTURE_CAPTURE = 1;
private String pictureFilePath;
Bitmap bitmap;
ImageView mImgView;
ArrayList<String> projectCodes = new ArrayList<String>();
ArrayList<String> assetTypeNames = new ArrayList<String>();
public static AssetRegistrationInfoFragment create(String key) {
Bundle args = new Bundle();
args.putString(ARG_KEY, key);
AssetRegistrationInfoFragment fragment = new AssetRegistrationInfoFragment();
fragment.setArguments(args);
return fragment;
}
public AssetRegistrationInfoFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
mKey = args.getString(ARG_KEY);
mPage = (AssetRegistrationInfoPage) mCallbacks.onGetPage(mKey);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
boolean cancel = false;
mAssetTypeName.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
mPage.getData().putString(AssetRegistrationInfoPage.ASSET_TYPE_NAME_KEY,
(editable != null) ? editable.toString() : null);
mPage.notifyDataChanged();
}
});
mAssetID.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
mPage.getData().putString(AssetRegistrationInfoPage.ASSET_ID_DATA_KEY,
(editable != null) ? editable.toString() : null);
mPage.notifyDataChanged();
}
});
mProjectCode.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
mPage.getData().putString(AssetRegistrationInfoPage.PROJECT_CODE_DATA_KEY,
(editable != null) ? editable.toString() : null);
mPage.notifyDataChanged();
}
});
mRemark.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
mPage.getData().putString(AssetRegistrationInfoPage.REMARK_DATA_KEY,
(editable != null) ? editable.toString() : null);
mPage.notifyDataChanged();
}
});
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_page_asset_registration_info, container, false);
((TextView) view.findViewById(android.R.id.title)).setText(mPage.getTitle());
getAssetType();
mAssetTypeName = ((AutoCompleteTextView) view.findViewById(R.id.assetTypeName));
mAssetTypeName.setText(mPage.getData().getString(AssetRegistrationInfoPage.ASSET_TYPE_NAME_KEY));
ArrayAdapter<String> assetTypeNameAdapter = new ArrayAdapter<String>
(getActivity(), android.R.layout.simple_dropdown_item_1line, assetTypeNames);
mAssetTypeName.setAdapter(assetTypeNameAdapter);
mAssetID = ((TextView) view.findViewById(R.id.assetID));
mAssetID.setText(mPage.getData().getString(AssetRegistrationInfoPage.ASSET_ID_DATA_KEY));
getProjects();
mProjectCode = ((AutoCompleteTextView) view.findViewById(R.id.projectCode));
mProjectCode.setText(mPage.getData().getString(AssetRegistrationInfoPage.PROJECT_CODE_DATA_KEY));
mProjectCode.setThreshold(1);
ArrayAdapter<String> projectCodeAdapter = new ArrayAdapter<String>
(getActivity(), android.R.layout.simple_dropdown_item_1line, projectCodes);
mProjectCode.setAdapter(projectCodeAdapter);
mRemark = ((TextView) view.findViewById(R.id.remark));
mRemark.setText(mPage.getData().getString(AssetRegistrationInfoPage.REMARK_DATA_KEY));
return view;
}
private void getProjects() {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<List<Project>> call = apiService.getProjects("");
call.enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
for (Project project : response.body()) {
projectCodes.add(project.getProjectCode());
}
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
Toast.makeText(getActivity(), "Unable to fetch Data " , Toast.LENGTH_LONG).show();
}
});
}
private void getAssetType() {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<List<AssetType>> call = apiService.getAssetTypes("");
call.enqueue(new Callback<List<AssetType>>() {
#Override
public void onResponse(Call<List<AssetType>> call, Response<List<AssetType>> response) {
for (AssetType assetType : response.body()) {
assetTypeNames.add(assetType.getName());
}
}
#Override
public void onFailure(Call<List<AssetType>> call, Throwable t) {
Toast.makeText(getActivity(), "Unable to fetch Data ", Toast.LENGTH_LONG).show();
}
});
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof PageFragmentCallbacks)) {
throw new ClassCastException("Activity must implement PageFragmentCallbacks");
}
mCallbacks = (PageFragmentCallbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
// In a future update to the support library, this should override setUserVisibleHint
// instead of setMenuVisibility.
if ( mAssetTypeName != null && mAssetID != null && mProjectCode != null && mRemark != null){
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (!menuVisible) {
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
}
}
}
}
The second page
public class AssetRegConfirmationPage extends Page {
public static final String ASSET_TYPE_NAME_KEY = "assetTypeName";
public AssetRegConfirmationPage(ModelCallbacks callbacks, String title) {
super(callbacks, title);
}
#Override
public Fragment createFragment() {
return AssetRegConfirmationFragment.create(getKey());
}
#Override
public void getReviewItems(ArrayList<ReviewItem> dest) {
dest.add(new ReviewItem("Nama Asset", mData.getString(ASSET_TYPE_NAME_KEY), getKey(), 0, null));
}
}
The second fragment
public class AssetRegConfirmationFragment extends ListFragment implements ModelCallbacks {
private static final String ARG_KEY = "key";
private Callbacks mCallbacks;
private AbstractWizardModel mWizardModel;
private List<ReviewItem> mCurrentReviewItems;
private ReviewAdapter mReviewAdapter;
public AssetRegConfirmationFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReviewAdapter = new ReviewAdapter();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_page, container, false);
TextView titleView = (TextView) rootView.findViewById(android.R.id.title);
titleView.setText(R.string.review);
titleView.setTextColor(getResources().getColor(R.color.colorPrimaryOld));
ListView listView = (ListView) rootView.findViewById(android.R.id.list);
setListAdapter(mReviewAdapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
return rootView;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new ClassCastException("Activity must implement fragment's callbacks");
}
mCallbacks = (Callbacks) activity;
mWizardModel = mCallbacks.onGetModel();
mWizardModel.registerListener(this);
onPageTreeChanged();
}
#Override
public void onPageTreeChanged() {
onPageDataChanged(null);
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
mWizardModel.unregisterListener(this);
}
#Override
public void onPageDataChanged(Page changedPage) {
ArrayList<ReviewItem> reviewItems = new ArrayList<ReviewItem>();
for (Page page : mWizardModel.getCurrentPageSequence()) {
page.getReviewItems(reviewItems);
}
Collections.sort(reviewItems, new Comparator<ReviewItem>() {
#Override
public int compare(ReviewItem a, ReviewItem b) {
return a.getWeight() > b.getWeight() ? +1 : a.getWeight() < b.getWeight() ? -1 : 0;
}
});
mCurrentReviewItems = reviewItems;
if (mReviewAdapter != null) {
mReviewAdapter.notifyDataSetInvalidated();
}
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
mCallbacks.onEditScreenAfterReview(mCurrentReviewItems.get(position).getPageKey());
}
public interface Callbacks {
AbstractWizardModel onGetModel();
void onEditScreenAfterReview(String pageKey);
}
private class ReviewAdapter extends BaseAdapter {
#Override
public boolean hasStableIds() {
return true;
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public boolean areAllItemsEnabled() {
return true;
}
#Override
public Object getItem(int position) {
return mCurrentReviewItems.get(position);
}
#Override
public long getItemId(int position) {
return mCurrentReviewItems.get(position).hashCode();
}
#Override
public View getView(int position, View view, ViewGroup container) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View rootView = inflater.inflate(R.layout.list_item_review, container, false);
ReviewItem reviewItem = mCurrentReviewItems.get(position);
String value = reviewItem.getDisplayValue();
if (TextUtils.isEmpty(value)) {
value = "(None)";
}
((TextView) rootView.findViewById(android.R.id.text1)).setText(reviewItem.getTitle());
((TextView) rootView.findViewById(android.R.id.text2)).setText(value);
return rootView;
}
#Override
public int getCount() {
return mCurrentReviewItems.size();
}
}
public static AssetRegConfirmationFragment create(String key) {
Bundle args = new Bundle();
args.putString(ARG_KEY, key);
AssetRegConfirmationFragment fragment = new AssetRegConfirmationFragment();
fragment.setArguments(args);
return fragment;
}
}
I have no idea of what should I do to make this work.
Here is the Page source code:
public abstract class Page implements PageTreeNode {
/**
* The key into {#link #getData()} used for wizards with simple (single) values.
*/
public static final String SIMPLE_DATA_KEY = "_";
protected ModelCallbacks mCallbacks;
/**
* Current wizard values/selections.
*/
protected Bundle mData = new Bundle();
protected String mTitle;
protected boolean mRequired = false;
protected String mParentKey;
protected Page(ModelCallbacks callbacks, String title) {
mCallbacks = callbacks;
mTitle = title;
}
public Bundle getData() {
return mData;
}
public String getTitle() {
return mTitle;
}
public boolean isRequired() {
return mRequired;
}
void setParentKey(String parentKey) {
mParentKey = parentKey;
}
#Override
public Page findByKey(String key) {
return getKey().equals(key) ? this : null;
}
#Override
public void flattenCurrentPageSequence(ArrayList<Page> dest) {
dest.add(this);
}
public abstract Fragment createFragment();
public String getKey() {
return (mParentKey != null) ? mParentKey + ":" + mTitle : mTitle;
}
public abstract void getReviewItems(ArrayList<ReviewItem> dest);
public boolean isCompleted() {
return true;
}
public Bitmap getPicture() {
return mData.getParcelable(getKey() + "_" + getTitle().trim());
}
public void resetData(Bundle data) {
mData = data;
notifyDataChanged();
}
public void notifyDataChanged() {
mCallbacks.onPageDataChanged(this);
}
public Page setRequired(boolean required) {
mRequired = required;
return this;
}
}
Look at this code in second fragment:
if (!(activity instanceof Callbacks)) {
throw new ClassCastException("Activity must implement fragment's callbacks");
}
Your activity where you using second fragment should implement Callbacks interface, like:
class YourActivity implements Callbacks {
}

Viewmodel shows empty recyclerview when activity rotated

I am pretty new to the Android architecture components and have been trying out room for data storage from my server. Problem is no data is being shown on the recycler view IMMEDIATELY. There's a searchview(no logic implemented just there) right above my recyclerview and when I click searchview for input, the recyclerview shows all the data which was supposed to be shown earlier.
RestaurantsAdapter:
public class RestaurantsAdapter extends RecyclerView.Adapter<RestaurantsAdapter.MyViewHolder> {
private List<Restaurant> data;
private Context context;
private LayoutInflater layoutInflater;
private final Random r = new Random();
public RestaurantsAdapter(Context context) {
this.data = new ArrayList<>();
this.context = context;
this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public RestaurantsAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_restaurant, parent, false);
return new RestaurantsAdapter.MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RestaurantsAdapter.MyViewHolder holder, int position) {
holder.rName.setText(data.get(position).getName());
}
public void setData(List<Restaurant> newData) {
if (data != null) {
RestaurantDiffCallback restaurantDiffCallback = new RestaurantDiffCallback(data, newData);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(restaurantDiffCallback);
data.clear();
data.addAll(newData);
diffResult.dispatchUpdatesTo(this);
} else {
// first initialization
data = newData;
}
}
#Override
public int getItemCount() {
return data.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView rName;
public MyViewHolder(View itemView) {
super(itemView);
rName = (TextView) itemView.findViewById(R.id.restaurant_name);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
}
}
class RestaurantDiffCallback extends DiffUtil.Callback {
private final List<Restaurant> oldRestaurants, newRestaurants;
public RestaurantDiffCallback(List<Restaurant> oldPosts, List<Restaurant> newPosts) {
this.oldRestaurants = oldPosts;
this.newRestaurants = newPosts;
}
#Override
public int getOldListSize() {
return oldRestaurants.size();
}
#Override
public int getNewListSize() {
return newRestaurants.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldRestaurants.get(oldItemPosition).getIdentifier().equals(newRestaurants.get(newItemPosition).getIdentifier());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldRestaurants.get(oldItemPosition).equals(newRestaurants.get(newItemPosition));
}
}}
MainActivity:
public class MainActivity extends AppCompatActivity {
private RestaurantsAdapter restaurantsAdapter;
private RestaurantViewModel restaurantViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
restaurantsAdapter = new RestaurantsAdapter(this);
restaurantViewModel = ViewModelProviders.of(this).get(RestaurantViewModel.class);
restaurantViewModel.getAllRestaurants().observe(this, restaurants -> restaurantsAdapter.setData(restaurants));
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(restaurantsAdapter);
}}
ViewModel:
public class RestaurantViewModel extends AndroidViewModel {
private RestaurantDao restaurantDao;
private ExecutorService executorService;
private ApiInterface webService;
public RestaurantViewModel(#NonNull Application application) {
super(application);
restaurantDao = RestaurantsDatabase.getInstance(application).restaurantDao();
executorService = Executors.newSingleThreadExecutor();
webService = ApiClient.getApiClient().create(ApiInterface.class);
}
LiveData<List<Restaurant>> getAllRestaurants() {
refreshUser();
return restaurantDao.findAll();
}
private void refreshUser() {
executorService.execute(() -> {
int numOfRestaurants = restaurantDao.totalRestaurants();
if (numOfRestaurants < 30) {
Call<RestaurantsModel> call = webService.getRestaurants();
call.enqueue(new Callback<RestaurantsModel>() {
#Override
public void onResponse(#NonNull Call<RestaurantsModel> call, #NonNull Response<RestaurantsModel> response) {
restaurantDao.saveAll(response.body().getData().getData());
}
#Override
public void onFailure(#NonNull Call<RestaurantsModel> call, #NonNull Throwable t) {
}
});
}
});
}}
If you don't use the DiffUtil with its diffResult.dispatchUpdatesTo(this); you should do notifyDataSetChanged(). In your case, in RestaurantsAdapter.setData add one line:
// first initialization
data = newData;
notifyDataSetChanged();
You have an issue in your setData method:
public void setData(List<Restaurant> newData) {
if (data != null) {
RestaurantDiffCallback restaurantDiffCallback = new RestaurantDiffCallback(data, newData);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(restaurantDiffCallback);
data.clear();
data.addAll(newData);
diffResult.dispatchUpdatesTo(this);
} else {
// first initialization
data = newData;
}
}
When your newData is null your change the data source of your adapter, but you don't call notifyDataSetChanged.
This way the data that you are seeing on the screen will not be updated.
So in order to fix it:
public void setData(List<Restaurant> newData) {
if (data != null) {
RestaurantDiffCallback restaurantDiffCallback = new RestaurantDiffCallback(data, newData);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(restaurantDiffCallback);
data.clear();
data.addAll(newData);
diffResult.dispatchUpdatesTo(this);
} else {
// first initialization
data = newData;
notifyDataSetChanged();
}
}
Another thing, if not a very good practice setting your adapter dataset has null. So my suggestion is to set your data as an empty list instead of null:
data = new ArrayList<>();
The issue: you're using ViewModels incorrectly. You are only returning data from the ViewModel, but never saving data in the ViewModel
Start by reading how ViewModels work here: https://developer.android.com/topic/libraries/architecture/viewmodel

Network Paging With pagedList in arch library

Android add Paging Library as part of Architecture Component for using the pagination of the resource. I use this example for do it. I write these codes but in my DataSource.Factory I can't return my received DataSource from network. let's see my codes:
PostDataSource:
public class PostDataSource extends PageKeyedDataSource {
private final APIUtils api = RetrofitHelper.getInstance().create(APIUtils.class);
private final String TOKEN = TokenUtil.getCachedAuthToken();
private int page = 1;
private GeneralWebResult<ArrayList<Post>> postList;
#Override
public void loadInitial(#NonNull LoadInitialParams params, #NonNull LoadInitialCallback callback) {
api.getMainPosts("all",10,"new",page,TOKEN).enqueue(new Callback<GeneralWebResult<ArrayList<Post>>>() {
#Override
public void onResponse(Call<GeneralWebResult<ArrayList<Post>>> call, Response<GeneralWebResult<ArrayList<Post>>> response) {
postList = response.body();
callback.onResult(/*return ArrayList<Post> */postList.getResult(),null, ++ page);
Log.e("RESULT",response.code()+" ");
}
#Override
public void onFailure(Call<GeneralWebResult<ArrayList<Post>>> call, Throwable t) {
Log.e("ERROR",t.getLocalizedMessage());
}
});
}
#Override
public void loadBefore(#NonNull LoadParams params, #NonNull LoadCallback callback) {
}
#Override
public void loadAfter(#NonNull LoadParams params, #NonNull LoadCallback callback) {
ArrayList<Post> postList = new ArrayList<>();
api.getMainPosts("all",10,"new",page,TOKEN).enqueue(new Callback<GeneralWebResult<ArrayList<Post>>>() {
#Override
public void onResponse(Call<GeneralWebResult<ArrayList<Post>>> call, Response<GeneralWebResult<ArrayList<Post>>> response) {
postList.addAll(response.body().getResult());
callback.onResult(postList,null);
Log.e("RESULT",response.code()+" ");
}
#Override
public void onFailure(Call<GeneralWebResult<ArrayList<Post>>> call, Throwable t) {
Log.e("ERROR",t.getLocalizedMessage());
}
});
}
}
PostDataSourceFactory:
public class PostDataSourceFactory extends DataSource.Factory {
private PostDataSource dataSource;
#Override
public DataSource create() {
return new PostDataSource();
}
}
PostViewModel:
public class PostViewModel extends ViewModel {
public LiveData<PagedList<Post>> liveDataSource;
public PostViewModel() {
Executor executor = Executors.newFixedThreadPool(5);
PostDataSourceFactory factory = new PostDataSourceFactory(executor);
PagedList.Config config = new PagedList.Config.Builder().setEnablePlaceholders(false)
.setInitialLoadSizeHint(10).setPageSize(10).setPrefetchDistance(4).build();
liveDataSource = (new LivePagedListBuilder(factory,config)).setFetchExecutor(executor).build();
//Log.e("LIVE DATA SOURCE", liveDataSource.getValue().get(0).getContent());
}
}
PostAdapter:
public class PostAdapter extends PagedListAdapter<Post, PostAdapter.Holder> {
private PagedList<Post> items;
public void setItems(PagedList<Post> items) {
this.items = items;
}
public class Holder extends RecyclerView.ViewHolder{
TextView content;
Holder(View itemView) {
super(itemView);
content = itemView.findViewById(R.id.item_post_test_content);
}
void bindTo(Post post){
content.setText(post.getContent());
}
}
PostAdapter() {
super(Post.DiffCAllback);
}
#NonNull
#Override
public PostAdapter.Holder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new Holder(LayoutInflater.from(parent.getContext()).inflate(R.layout.post_test_item,parent,false));
}
#Override
public void onBindViewHolder(#NonNull PostAdapter.Holder holder, int position) {
holder.bindTo(getItem(position));
}
}
MainActivity:
list = findViewById(R.id.home_list);
list.setLayoutManager(new LinearLayoutManager(this));
PostViewModel viewModel = ViewModelProviders.of(this).get(PostViewModel.class);
PostAdapter adapter = new PostAdapter();
viewModel.liveDataSource.observe(this,items ->{
Log.e("ITEMS SIZE",items.size()+" ");
adapter.setItems(items);
} );
list.setAdapter(adapter);
Where is my mistake? why PostDataSourceFactory return null on create()?
Use a MutableLiveData object to wrap your data source
MutableLiveData is used for notifying your UI when observing any data
When using LivePagedListBuilder it is advisable to use a MutableLiveData as a wrapper for data source
See PagingWithNetworkSample
You also need to update your data source PagedKeyDataSource
public class PostDataSourceFactory extends DataSource.Factory<Integer, Post> {
public MutableLiveData<PostDataSource> datasourceLiveData = new MutableLiveData<>();
public PostDataSourceFactory(
}
#Override
public DataSource<Integer, Post> create() {
PostDataSource dataSource = new PostDataSource();
datasourceLiveData.postValue(dataSource);
return dataSource;
}
}

how to use Architecture Components ViewModel inside RecyclerView Adapter?

I have multiple ViewHolders that work as separated views inside a vertical RecyclerView. I'm practicing with the new Architecture Components ViewModel.
Inside my ViewModel I have a couple of MutableLiveData lists that i want to observe, but how can I call
ViewModelProviders.of((AppCompatActivity)getActivity()).get(FilterViewModel.class)
and
mFilterViewModel.getCountries().observe(this, new Observer<ArrayList<TagModel>>() {
#Override
public void onChanged(#Nullable ArrayList<TagModel> tagModels) {
}
});
without leaking the activity or save the activity inside the adapter?
my ViewModel
public class FilterViewModel extends ViewModel {
private final MutableLiveData<ArrayList<TagModel>> mCountries;
private final MutableLiveData<ArrayList<TagModel>> mSelectedCountryProvinceList;
private final MutableLiveData<ArrayList<TagModel>> mDistanceList;
public FilterViewModel(){
mCountries = new MutableLiveData<>();
mSelectedCountryProvinceList = new MutableLiveData<>();
mDistanceList = new MutableLiveData<>();
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mCountries.setValue(object.getCountries());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 5){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(5,"parent");
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mSelectedCountryProvinceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 6){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(6,"parent");
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mDistanceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 51){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(51,"parent");
}
public void selectCountry(final TagModel country){
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mSelectedCountryProvinceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == country.getId()){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(country.getId(),"parent");
}
public LiveData<ArrayList<TagModel>> getCountries(){
return mCountries;
}
public LiveData<ArrayList<TagModel>> getDistances(){
return mDistanceList;
}
public LiveData<ArrayList<TagModel>> getProvinces(){
return mSelectedCountryProvinceList;
}
I am using Room Persistence library. Below is my code for recyclerview adapter using MVVM.
You can see CartViewModel and I have initialized it into the constructor. The constructor gets the context from the activity, and I have cast it into FragmentActivity.
private CartViewModel cartViewModel;
public CartListAdapter(Context context, List<CartModel> cartModels) {
this.context = context;
this.cartModels = cartModels;
cartViewModel = ViewModelProviders.of((FragmentActivity) context).get(CartViewModel.class);
}
Here is my full adapter class. I hope it will help.
public class CartListAdapter extends RecyclerView.Adapter<CartListAdapter.CartListViewHolder> {
private static final String TAG = "CartListAdapter";
private Context context;
private List<CartModel> cartModels;
private Double totalQuantity = 0.0;
private CartViewModel cartViewModel;
public CartListAdapter(Context context, List<CartModel> cartModels) {
this.context = context;
this.cartModels = cartModels;
cartViewModel = ViewModelProviders.of((FragmentActivity) context).get(CartViewModel.class);
}
#NonNull
#Override
public CartListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CartListViewHolder(LayoutInflater.from(context).inflate(R.layout.list_all_cart_item,parent,false));
}
#Override
public void onBindViewHolder(#NonNull CartListViewHolder holder, int position) {
CartModel cartModel = cartModels.get(position);
Glide.with(context)
.load(cartModel.getPPICLocate())
.into(holder.cartItemImage);
holder.tvCartProductName.setText(cartModel.getProductName());
holder.tvCartProductCategory.setText(cartModel.getPCategorySubID());
holder.tvCartProductPrice.setText(cartModel.getPPriceSales());
holder.etCartProductQuantity.setText(cartModel.getPQuantity());
holder.btnCartPQtIncrease.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
totalQuantity = Double.valueOf(holder.etCartProductQuantity.getText().toString());
totalQuantity = totalQuantity+1;
cartModel.setPQuantity(totalQuantity.toString());
updateCart(cartModel);
}
});
holder.btnCartPQtDecrease.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
totalQuantity = Double.valueOf(holder.etCartProductQuantity.getText().toString());
totalQuantity = totalQuantity-1;
cartModel.setPQuantity(totalQuantity.toString());
updateCart(cartModel);
}
});
}
#Override
public int getItemCount() {
return cartModels.size();
}
public class CartListViewHolder extends RecyclerView.ViewHolder{
private ImageView cartItemImage;
private TextView tvCartProductName,tvCartProductCategory,tvCartProductPrice,
etCartProductQuantity,tvCartProductPrevPrice;
private ImageButton btnCartPQtIncrease,btnCartPQtDecrease;
public CartListViewHolder(#NonNull View itemView) {
super(itemView);
cartItemImage= itemView.findViewById(R.id.cartItemImage);
tvCartProductName= itemView.findViewById(R.id.tvCartProductName);
tvCartProductCategory= itemView.findViewById(R.id.tvCartProductCategory);
tvCartProductPrice= itemView.findViewById(R.id.tvCartProductPrice);
etCartProductQuantity= itemView.findViewById(R.id.etCartProductQuantity);
tvCartProductPrevPrice= itemView.findViewById(R.id.tvCartProductPrevPrice);
btnCartPQtIncrease= itemView.findViewById(R.id.btnCartPQtIncrease);
btnCartPQtDecrease= itemView.findViewById(R.id.btnCartPQtDecrease);
}
}
public void addItems(List<CartModel> cartModels) {
this.cartModels = cartModels;
notifyDataSetChanged();
}
private void updateCart(CartModel cartModel){
String tqt = String.valueOf(cartModel.getPQuantity());
Log.d(TAG, "updateQuantity: "+tqt);
/*cartRepository.updateCartRepo(cartModel);*/
cartViewModel.updateCartItemVM(cartModel);
}
}
You could create an OnClickListener interface instead, then implement the onClick method (defined in your interface) in your fragment or activity where you have access to your view model.
My solution to this issue is;
create a new variable(ViewModel) for the layout (fragment or activity layout)
in your activity or fragment get the instance of your viewModel with ViewModelProviders
hold the data which fills your recyclerView in MutableLiveData and observe it and fill the adapter of recyclerView with the value you observed
here you mut be careful not to create adapter instance in observe method.
if you create it in observe method, it will make leaks

Categories