Problems with intance of my DataReader Class - java

I have some trouwble with my custom class to get Data. I've created one custom class for get data from my web service, the issue comes when I try to use my singleton class, If I use DataReader.getInstance(getContext(), this) my listview is populated only the first time when app starts. But if I use a new instance of my DataReader work fine in anytime..
Here my DataReader Class:
public class DataReader {
private static DataReader singleton;
private Context context;
private Data_Listener data_listener;
public DataReader(Context context, Data_Listener data_listener) {
this.context = context;
this.data_listener = data_listener;
}
public static synchronized DataReader getInstance(Context context, Data_Listener data_listener) {
if (singleton == null) {
singleton = new DataReader(context, data_listener);
}
return singleton;
}
public void Categorias_fill(){
final ArrayList<Object> objects = new ArrayList<>();
API.getInstance(context).unAuthenticateArrayRequest(Request.Method.GET, context.getString(R.string.endpoint_categorias), null, new API_Listener() {
#Override
public void OnSuccess(JSONObject response) {
}
#Override
public void OnSuccess(JSONArray response) throws JSONException {
Gson gson = new Gson();
Categoria categoria;
for(int i = 0;i < response.length(); i++) {
categoria = gson.fromJson(response.getJSONObject(i).toString(), Categoria.class);
objects.add(categoria);
}
data_listener.onBindData(objects);
}
#Override
public void OnError(String error) {
data_listener.onBindData(objects);
}
});
}
and here how I used DataReader:
public class CategoriasFragment extends Fragment implements Data_Listener{
private Categorias_Adapter adapter;
private ArrayList<Categoria> list = new ArrayList<>();
private ExpandableListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_categorias, container, false);
listView = (ExpandableListView) view.findViewById(R.id.exp_list);
listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition, long id) {
if(list.size()>0){
if(list.get(groupPosition).getSubCategorias().size()>0){
SubCategoria subCategoria = list.get(groupPosition).getSubCategorias().get(childPosition);
Log.d(General.appname, subCategoria.getNombre());
}
}
return true;
}
});
//This work only the first time
//DataReader.getInstance(getContext(), this).Categorias_fill();
//This work everytime
DataReader dataReader = new DataReader(getContext(), this);
dataReader.Categorias_fill();
return view;
}
#Override
public void onBindData(ArrayList<Object> objects) {
list = (ArrayList) objects;
adapter = new Categorias_Adapter(getContext(), list);
listView.setAdapter(adapter);
}

In subsequent calls to getInstance() your context and Data_Listener are not getting set. They'll still point to whatever they pointed to with the first call.
I suggest trying this: explicitly set DataReader.context and DataReader.data_listener inside your getInstance() method.

Related

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

Parcelable change the orginal data

I am implementing RecycleView that will display list of skills those skills passed form another fragment using Parcelable the issue that when I delete item from the skills that display on RecycleView and back to the previous fragment the item also deleted from the orginal arraylist
Here is the Parcelable object
public class Skills implements Parcelable {
private ArrayList<String> skills;
public ArrayList<String> getVacancySkills() {
return skills;
}
public void setSkills(ArrayList<String> skills) {
this.skills = skills;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringList(this.skills);
}
public Skills() {
}
protected Skills(Parcel in) {
this.skills = in.createStringArrayList();
}
public static final Parcelable.Creator<Skills> CREATOR = new Parcelable.Creator<Skills>() {
#Override
public Skills createFromParcel(Parcel source) {
return new Skills(source);
}
#Override
public Skills[] newArray(int size) {
return new Skills[size];
}
};
}
and here is the passing data
#OnClick(R.id.card_add_skill)
public void showAddSkills() {
Bundle data = new Bundle();
Skills skills = new Skills();
skills.setSkills(skillsLit);
data.putParcelable(ExtraKeys.VACANCY_SKILLS, skills);
VacancyEditSkillsFragment vacancyEditSkillsFragment = new VacancyEditSkillsFragment();
FragmentUtils.addFragment(
getActivity().getSupportFragmentManager(),
vacancyEditSkillsFragment
,
true,
App.getContext().getString(R.string.skills), data
);
}
and here is the fragment that recive the Parcable arraylist of strings
public class VacancyEditSkillsFragment extends Fragment {
#BindView(R.id.rc_edit_skill_recycle)
RecyclerView editSkillsRecycle;
private Skills mSkills;
private EditVacancySkillsAdapter mEditVacancySkillsAdapter;
public VacancyEditSkillsFragment() {
// Required empty public constructor
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getArguments() != null) {
mSkills = getArguments().getParcelable(ExtraKeys.VACANCY_SKILLS);
}
mEditVacancySkillsAdapter = new EditVacancySkillsAdapter(mSkills.getVacancySkills());
editSkillsRecycle.setLayoutManager(new LinearLayoutManager(getActivity()));
editSkillsRecycle.setAdapter(mEditVacancySkillsAdapter);
}
public static VacancyEditSkillsFragment newInstance() {
VacancyEditSkillsFragment fragment = new VacancyEditSkillsFragment();
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_vacancy_edit_skills, container, false);
ButterKnife.bind(this, view);
return view;
}
}
and here is the adapter code
public class EditVacancySkillsAdapter extends RecyclerView.Adapter<EditVacancySkillsAdapter.EditSkillViewHolder> {
private List<String> skills;
public class EditSkillViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#BindView(R.id.tv_skill_name)
TextView skillTitle;
#BindView(R.id.iv_reomve_skill)
ImageView deleteSkill;
public EditSkillViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
deleteSkill.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if(view.getId()== R.id.iv_reomve_skill)
{
skills.remove(getAdapterPosition());
notifyDataSetChanged();
}
}
}
public EditVacancySkillsAdapter(ArrayList<String> sections) {
this.skills = sections;
if (skills == null) {
skills = new ArrayList<>();
}
}
#Override
public EditSkillViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_edit_vacancy_skill, parent, false);
return new EditSkillViewHolder(itemView);
}
#Override
public void onBindViewHolder(EditSkillViewHolder holder, int position) {
holder.skillTitle.setText(skills.get(position));
}
#Override
public int getItemCount() {
return skills.size();
}
}
This is as expected.
What is parcellable in simple term is:- It allows you to send a Object across different components. You are referencing the same object in both the components. Parcellable has implemented on top the object. So if any change happens to the object anywhere in the component will reflect back to rest of the component which uses the same object.
I hope you understood.
What you should do is send a copy of the object instead of the original object.
How to clone a parcellable object is by this:-
Foo foo1 = new Foo("a", "b", "c");
Parcel p = Parcel.obtain();
p.writeValue(foo1);
p.setDataPosition(0);
Foo foo2 = (Foo)p.readValue(Foo.class.getClassLoader());
p.recycle();
I hope this will help!

NullPointerException In my widgets inside a Fragment

When the method(Bill) is called the widgets that I initialized with Butterknife is returning null(noPrev and recyclerList).
In other classes like activities, I used the same method(Binding the widget with BUtterknife) and It is working well but when I tried binding my widgets inside the fragment, it always returning null.
........................................................................
public class Home extends Fragment implements BridgeInterface.BillsResponse{
#BindView(R.id.rvBillList)
RecyclerView recyclerList;
#BindView(R.id.tvNoPreviousBill)
TextView noPrev;
public List<Bill> list_of_bill = new ArrayList<>();
private BillRecyclerAdapter adapter;
private Handler h;
private Runnable r;
private View v;
public Home() {
// Required empty public constructor
}
public static Home getInstance() {
Home home = new Home();
return home;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
v = inflater.inflate(R.layout.fragment_home, container, false);
ButterKnife.bind(Home.this,v);
if(list_of_bill.size()==0){
noPrev.setVisibility(View.VISIBLE);
recyclerList.setVisibility(View.GONE);
} else{
noPrev.setVisibility(View.GONE);
recyclerList.setVisibility(View.VISIBLE);
}
return v;
}
#Override
public void onResume() {
super.onResume();
h = new Handler();
r = () -> {
BridgeInterface.GetBIlls getBIlls =
new BillsRequest(getContext());
getBIlls.request();
h.postDelayed(r,5000);
};
r.run();
}
#Override
public void Bill(List<Bill> bills) {
list_of_bill = bills;
initRecycler();
}
public void initRecycler() {
noPrev.setVisibility(View.GONE);
recyclerList.setVisibility(View.VISIBLE);
adapter = new BillRecyclerAdapter(list_of_bill);
recyclerList.setAdapter(adapter);
recyclerList.setLayoutManager(new LinearLayoutManager(getContext()));
}
}
Here's the Interface Class
public interface BridgeInterface {
interface BillsResponse{
void Bill(List<Bill> bills);
}
interface GetBIlls{
void request();
}
}
BillRequest Class;:::
public class BillsRequest implements BridgeInterface.GetBIlls {
private Context context;
private final String url = "http://"+
AppSingleton.getInstance().getIP_ADDRESS()
+"/waterdistrict/bills.php";
private Gson gson;
public BillsRequest(Context context ) {
this.context = context;
}
#Override
public void request() {
StringRequest request = new StringRequest(Request.Method.GET, url,
response -> {
gson = new GsonBuilder().create();
List<BillResponse> bill_response =
Arrays.asList(gson.fromJson(response,
BillResponse.class));
Log.d("Check", "Levl 2");
Home.getInstance().Bill(bill_response.get(0).getBills());
}, error -> {
Log.d("Error", error.toString());
});
RequestQueue queue = Volley.newRequestQueue(context);
queue.add(request);
}
}

Global Arraylist index out of range issue

I am facing an issue where I am getting ArrayIndexOutOfBound on photoViewAttacherList.get(position). Now I can easily handle by catching the error, but I am not able to understand why it is happening at the very first place. Here is the code of the adapter. It's strange because I am adding the new PhotoViewAttacher in instantiate function which will be called for every new item added.
public class FullImagePagerAdaptor extends PagerAdapter {
private final LayoutInflater inflater;
private final ArrayList<String> urls;
private final Context context;
private final ArrayList<String> descriptions;
private ArrayList<PhotoViewAttacher> photoViewAttacherList;
private TogglePagingListener togglePagingListener;
public FullImagePagerAdaptor(Context context, ArrayList<String> urls, ArrayList<String>
descriptions, TogglePagingListener togglePagingListener) {
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.urls = urls;
this.context = context;
this.descriptions = descriptions;
photoViewAttacherList = new ArrayList<>();
this.togglePagingListener = togglePagingListener;
}
#Override
public int getCount() {
return urls.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
View v = inflater.inflate(R.layout.fragment_full_image, container, false);
TextView description = (TextView) v.findViewById(R.id.txt_full_image_description);
ProgressBar progressBar = (ProgressBar) v.findViewById(R.id.progress_bar_full_image);
ImageView imageView = (ImageView) v.findViewById(R.id.full_image_view);
if (descriptions != null && descriptions.get(position) != null) {
description.setText(descriptions.get(position));
}
Log.d("sg","Instantiating Pager:with position" + position);
ImageUtility.loadImage(context, urls.get(position),
GlobalVariables.IMAGE_TYPE.URL, 0, 0,
imageView, progressBar);
photoViewAttacherList.add(new PhotoViewAttacher(imageView));
// THIS IS WHERE ARRAYINDEXOUTOFBOUND EXCEPTION IS GETTING RAISED
photoViewAttacherList.get(position).setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() {
#Override
public void onMatrixChanged(RectF rect) {
if(isImageZoomed(position))
togglePagingListener.disablePaging();
else
togglePagingListener.enablePaging();
}
});
container.addView(v);
return v;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
photoViewAttacherList = null;
container.removeView((View) object);
}
public boolean isImageZoomed(int pos) {
return checkZoom(pos) != 1.0;
}
public float checkZoom(int pos){
return photoViewAttacherList.get(pos).getScale();
}
public interface TogglePagingListener {
public void enablePaging();
public void disablePaging();
}
From your code I understand one thing that, you want to add a new PhotoViewAttacher object to your existing list and add a listener setOnMatrixChangeListener to that object.
For this you are adding that object in list and then accessing the same again from list, rather you can have an object created and add to list and to the same object you can add event listener. This is a clean approach.
position should also be recalculated to know exactly where the element was inserted but this is final so you can not change it so declare a new variable for it.
Snippet:
PhotoViewAttacher photoObj = new PhotoViewAttacher(imageView);
photoViewAttacherList.add(photoObj);
final int newPositionInserted = photoViewAttacherList.size() - 1; //Recalculating the position here to know exactly where the object was added
// Here you can use the object which was created so you do not need to worry about the index also
photoObj.setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() {
#Override
public void onMatrixChanged(RectF rect) {
if(isImageZoomed(newPositionInserted))
togglePagingListener.disablePaging();
else
togglePagingListener.enablePaging();
}
});

ListView doesn't show refreshed content

I have a custom adapter which extends from BaseAdapter..
Custom adapter code :
public class SearchListViewAdapter extends BaseAdapter {
private LayoutInflater layoutInflater;
private JsonArray searchResults;
public SearchListViewAdapter(Context context, JsonArray searchResults) {
this.searchResults = searchResults;
layoutInflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return searchResults.count();
}
#Override
public Object getItem(int position) {
return searchResults.get(position);
}
/*public ListAdapter updateResults(JsonArray results) {
searchResults = results;
notifyDataSetChanged();
return null;
}*/
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
JsonObject searchResult = (JsonObject)getItem (position);
ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.custom_search_result, null);
holder = new ViewHolder();
holder.txtFullName = (TextView) convertView.findViewById(R.id.FullName);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.txtFullName.setText(searchResult.getString ("FirstName") + searchResult.getString ("LastName"));
return convertView;
}
static class ViewHolder {
TextView txtFullName;
}
}
Activity code :
// After displaying the list in an onPostExecute Method of an AsyncTask class
// I call another async task : BarcodeAction by giving the param : records
new BarcodeAction(records).execute("");
private class BarcodeAction extends AsyncTask<String, Void, JsonArray> {
private JsonArray records;
public BarcodeAction(JsonArray result)
{
this.records = result;
}
#Override
protected JsonArray doInBackground(String... params) {
// Processing... if it's success the onPostExecute method receive : records
if (resultType.equals("success"))
return records;
return null;
}
#Override
protected void onPostExecute(final JsonArray records) {
final ListView lv1 = (ListView) findViewById(R.id.ListViewSearchResults);
// EDIT : notifyDataSetChange doesn't work
SearchListViewAdapter svla1 = new SearchListViewAdapter(SearchActivity.this, records);
lv1.setAdapter(svla1);
svla1.notifyDataSetChanged();
lv1.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Object o = lv1.getItemAtPosition(position);
JsonObject response = (JsonObject)o;
SearchActivity.VISITOR_BARCODE = response.getString("Barcode");
new BarcodeAction(records).execute("");
}
});
}
}
But my list is not getting refreshed..
Do you have any idea about this ? Thnak you.
getItemId() should return unique value for each entry in the list. Try returning position only.
I think the main cause is missing calling notifyDataSetChanged() method.
In fact, your "activity code" is not exactly the activity but a asnyctask that fetch the data. A more common way to use adaper is having a Adaper in your activty along with ListView. In your fetching-data-method(asynctask or loader), call the adaper's change underlying data interface to change the data, and call the adaper's notifyDataSetChanged() method.
A bit psudeo-code may looks like:
Adaper:
public class SearchListViewAdapter extends BaseAdapter {
private JsonArray searchResults;
......
public setDataSet(JsonArray newData) {
searchREsults = newData;
}
......
}
Activity:
public class MyActivity extends Activity {
ListView mResultListView;
SearchListViewAdaper mResultViewAdaper;
#override
OnCreate(...) {
......
//init mResultListView
mResultListView = (ListView) findViewById(R.id.xxxx);
mResultViewAdaper = new SearchListViewAdapter();
mResultListView.setAdapter(mResultViewAdaper);
......
}
......
}
AsyncTask:
public fetchDataTask extends AsyncTask {
......
onPostExecute(JsonArray records) {
mResultViewAdaper.setDataSet(records);
// IMPORTANT: notify data change
mResultViewAdaper.notifyDataSetChanged();
}

Categories