Android recycler view toggle button recycling issue - java

I am making an app and has a recyclerview for showing user feed. I am using a toggle button for liking posts. When the user opens the app it enables the like button if the status is already liked by the user who is using the app and the user can also like the posts by scrolling down like other socials apps out there.
The problem is if a toggle button is checked when scrolling down
other toggle buttons belongs to other posts are also getting checked.
Adapter class
public class topNewsAdapter extends RecyclerView.Adapter<topNewsRowHolder> {
private ArrayList<topData> topDataList;
private Context context;
private Activity activity;
private RecyclerView recyclerView;
private View v;
protected String liked = "success";
protected String expired = "Expired";
public topNewsAdapter( ArrayList<topData> listItemList , Activity activity , RecyclerView view) {
this.topDataList = listItemList;
this.activity = activity;
this.recyclerView = view;
}
#Override
public topNewsRowHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.top_news_row, parent, false);
context = parent.getContext();
return new topNewsRowHolder(v , activity); //passed activity
}
#Override
public void onBindViewHolder(final topNewsRowHolder holder, final int position) {
topData item = topDataList.get(position);
if(!(item == null)) {
holder.userName.setText(item.getUserName().toString());
holder.timer.setText(item.getCreatedTime().toString());
holder.status.setText(item.getStatus().toString());
holder.fameCount.setText(item.getLikeCount().toString());
holder.dislike_Count.setText(item.getDislike_Count().toString());
holder.statusID.setText(item.getStatusID().toString());
if(item.getLiked().equals(1)){
holder.fameButton.setChecked(true);
}else if(item.getLiked().equals(0)){
holder.fameButton.setChecked(false);
}
if(item.getDisliked().equals(1)){
holder.disLikeButton.setChecked(true);
}else if(item.getDisliked().equals(0)){
holder.disLikeButton.setChecked(false);
}
holder.fameButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
lRequester(holder.statusID.getText().toString() , "1" , holder.fameCount );
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
holder.fameButton.setChecked(true);
}else if(!isChecked){
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
}
}
});
holder.disLikeButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
dRequester(holder.statusID.getText().toString(), "1" ,holder.dislike_Count);
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
holder.fameButton.setChecked(false);
}else if(!isChecked){
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
}
}
});
}
}
#Override
public int getItemCount() {
return topDataList.size();
}
#Override
public long getItemId(int position) {
return position;
}
public void lRequester(String id , String type, final TextView textview){
Call<responseFD> request = handler.handlerClass.fame(data("u") , data("t") , id , type );
request.enqueue(new Callback<responseFD>() {
#Override
public void onResponse(Call<responseFD> call, Response<responseFD> response) {
if(!response.body().getResponse().isEmpty() && (response.body().getResponse() != null)) {
if (response.body().getResponse().equals(expired)) {
vlData.getInstance().terminateRunnable();
reset();
}else if (response.body().getResponse().equals(liked)) {
textview.setText(response.body().getLike_count().toString());
}
}
}
#Override
public void onFailure(Call<responseFD> call, Throwable t) {
Toast.makeText(v.getContext(), "Something Gone Wrong", Toast.LENGTH_SHORT).show();
}
});
}
public void dRequester(String id , String type , final TextView textView){
Call<responseFD> request = handler.handlerClass.disfame(data("u") , data("t") , id , type );
request.enqueue(new Callback<responseFD>() {
#Override
public void onResponse(Call<responseFD> call, Response<responseFD> response) {
if(!response.body().getResponse().isEmpty() && response.body().getResponse() != null) {
if (response.body().getResponse().equals(expired)) {
vlData.getInstance().terminateRunnable();
reset();
}else if (response.body().getResponse().equals(liked)) {
textView.setText(response.body().getDislike_Count().toString());
}
}
}
#Override
public void onFailure(Call<responseFD> call, Throwable t) {
Log.d("data", "onFailure: " + t.getMessage());
}
});
}
public void reset(){
Intent i = new Intent(activity , login.class);
activity.startActivity(i);
activity.finish();
}
}
Rowholder class
public class topNewsRowHolder extends RecyclerView.ViewHolder {
protected TextView userName;
protected TextView timer;
protected TextView status;
protected ImageView profilePicHolder;
protected TextView fameCount;
protected TextView dislike_Count;
protected ToggleButton fameButton;
protected ToggleButton disLikeButton;
protected View v;
protected TextView statusID;
protected Activity activity;
protected RelativeLayout holderlayout;
public topNewsRowHolder(View view , Activity activity ){
super(view);
v = view;
this.activity = activity;
this.userName = (TextView)view.findViewById(R.id.usernameHolder);
this.timer = (TextView)view.findViewById(R.id.timeHolder);
this.status = (TextView)view.findViewById(R.id.status_user);
this.profilePicHolder = (ImageView)view.findViewById(R.id.profile_pic_holder);
this.fameCount = (TextView)view.findViewById(R.id.like_count);
this.dislike_Count = (TextView)view.findViewById(R.id.dislike_count);
this.fameButton = (ToggleButton)view.findViewById(R.id.fameButton);
this.disLikeButton = (ToggleButton)view.findViewById(R.id.dislikeButton);
this.statusID = (TextView)view.findViewById(R.id.statusID);
this.holderlayout = (RelativeLayout) view.findViewById(R.id.text);
}
}
I know this is because of the recycling. How to fix this issue ?. Thanks :)

Try this : RecyclerView needs an external variable to keep track of which items are checked, while SparseBooleanArray will work, its tedious.
holder.fameButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean IsChecked) {
isChecked ? item.setIsLiked(1) : item.setIsLiked(0);
if(isChecked){
lRequester(holder.statusID.getText().toString() , "1" , holder.fameCount );
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
} else if(!isChecked){
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
}
}
});

Create a Broker pattern for managind likes within the content feed.
This singleton broken would keep track of each Content feed item id and map it to a state (social or just likes) in a hashmap. And every time there is a reaction (like/unlike/dislike/undislike) happenning to a Content feed item, you trigger the actian using this Broker that also propagates and keeps track of the changes locally. You ahould implement a Broadcaster or a pub-sub pattern to ibform the UI about the changes.
Preferably use a Content Provider with a database for storing the json you are getting from the webserver.
Let me know if you need further clarifications.
SocialBroker.java
public class SocialBroker extends ISocialBroker {
//region <!-- Private properties -->
private static SocialBroker _instance;
private HashMap<UUID, SocialStatus> _map = new HashMap<>();
//endregion
//region <!-- Singleton initializer -->
public static SocialBroker newInstance() {
if (_instance == null) {
_instance = new SocialBroker();
}
return _instance;
}
private SocialBroker() {};
//endregion
// The interface method implemented here
}
ISocialBroker.java
public interface ISocialBroker {
public synchronized SocialStatus save(String expiresAt, UUID id, boolean hasLiked, int numberOfLikes, int numberOfComments);
public SocialStatus lookup(UUID id);
public synchronized SocialStatus like(UUID id);
public synchronized SocialStatus unlike(UUID id);
public HashMap<UUID, SocialStatus> getMap();
public void loadState(HashMap<UUID, SocialStatus> restoredSocialState);
}
OnReactionObserver.java
public interface OnReactionObserver {
void onLikeReaction();
}
OnReactionPublisher.java
public interface OnReactionPublisher {
void attachObserver(OnReactionObserver... observer);
void notifyListeners();
}
When you bind the results to your views in your adapter, you save the entries using the broker, then in your like button view, you summon the Broker again to execute the like/unlike/dislike/undislike.
SocialStatus is just a model to keep track of information, that can be serialized, saved, synchronized with an online service.
Hope this points you to the right direction.

Related

Recyclerview Adapter not refreshing

I have a mind-boggling problem I can't seem to solve.
The data in my RecyclerView is not updating, and after an entire day of debugging, I can't find the problematic code. The API returns the correct data, and I parse the correct data in a wallItemList which I pass to the Adapter.
How It Should Behave
After changing the language setting to either one of the 2 (English or Dutch), the items in my Recyclerview should update with it and the title of the element should change to the translated string.
What I Have Tried
Creating a refresh function inside the adapter, and update the wallItemList manually by passing the created wallItemList from the MainActivity and calling notifyDataSetChanged()
Calling notifyDataSetChanged() before, in and after the OnClickListener in the MyRecyclerViewAdapter
Setting the item in onBindViewHolder in the MyRecyclerViewAdapter
Strangely enough, when logging the language of the wallItem just before adapter.setOnItemClickListener in populateRecyclerView(), the language is right. But when I get the string from the object in MyRecyclerViewAdapter's onBindViewHolder, it shows the wrong language.
Here is my MainActivity.java:
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private List<WallItem> WallItemList;
private RecyclerView mRecyclerView;
private MyRecyclerViewAdapter adapter;
private ProgressBar progressBar;
// LifeCycle variables
private String JSONResults = "";
final static private String JSON_KEY_RESULTS = "";
final static private String WALL_ITEM_LIST_KEY = "";
// SharedPrefences variables
private String APIUrlPreferenceString = "";
private String langPreferenceString = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
// Setup shared preferences
setupSharedPreferences();
// Load the recyclerView
loadRecyclerView(savedInstanceState);
}
private void setLanguageSettings(String lang)
{
//create a string for country
String country = "";
if(lang.equals("en"))
{
country = "EN";
}
else if(lang.equals("nl"))
{
country = "NL";
}
//use constructor with country
Locale locale = new Locale(lang, country);
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
}
private void setupSharedPreferences()
{
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
APIUrlPreferenceString = sharedPreferences.getString(getString(R.string.pref_api_url_key), getString(R.string.pref_api_url_def_value));
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
// Language settings
if(sharedPreferences.getBoolean(getString(R.string.pref_lang_check_key), true))
{
// Use device settings
setLanguageSettings(Resources.getSystem().getConfiguration().locale.getLanguage());
langPreferenceString = Resources.getSystem().getConfiguration().locale.getLanguage();
}
else
{
// Use preference settings
setLanguageSettings(sharedPreferences.getString(getString(R.string.pref_lang_list_key), getString(R.string.pref_lang_label_en)));
langPreferenceString = sharedPreferences.getString(getString(R.string.pref_lang_list_key), getString(R.string.pref_lang_label_en));
}
}
private void loadRecyclerView(Bundle savedInstanceState)
{
// Lifecycle event to preserve data to prevent repeating API calls
if(savedInstanceState != null && savedInstanceState.containsKey(WALL_ITEM_LIST_KEY) && savedInstanceState.containsKey(JSON_KEY_RESULTS))
{
progressBar.setVisibility(View.GONE);
// Set again in order to preserve state on future rotations
JSONResults = savedInstanceState.getString(JSON_KEY_RESULTS);
// Set wallItemList again in order to preserve state on future rotations
WallItemList = savedInstanceState.getParcelableArrayList(WALL_ITEM_LIST_KEY);
populateRecyclerView();
}
else
{
// First execution
new DownloadTask().execute();
}
}
public class DownloadTask extends AsyncTask<Void, Void, Boolean> {
#Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
}
#Override
protected Boolean doInBackground(Void... params) {
boolean result;
String blindWallResults;
try {
// Error fix, because NetworkUtils.buildUrl returns null when failing
if(null == NetworkUtils.buildUrl(APIUrlPreferenceString))
return false;
// Get response from API
blindWallResults = NetworkUtils.getResponseFromHttpUrl(NetworkUtils.buildUrl(APIUrlPreferenceString));
// Send to parser
JSONResults = blindWallResults;
parseResult(blindWallResults);
result = true;
} catch (IOException e) {
e.printStackTrace();
result = false;
}
// When failed
return result;
}
#Override
protected void onPostExecute(Boolean result) {
progressBar.setVisibility(View.GONE);
// If succeeded
if (result) {
populateRecyclerView();
// Show toast when data has been loaded for the first time
Toast.makeText(MainActivity.this, getString(R.string.json_toast_data_loaded), Toast.LENGTH_SHORT).show();
} else {
// If failed make toast
Toast.makeText(MainActivity.this, getString(R.string.json_toast_data_failed), Toast.LENGTH_SHORT).show();
}
}
}
/**
* Populates recyclerView and adds OnItemClickListener
*/
private void populateRecyclerView()
{
WallItem w = WallItemList.get(0);
adapter = new MyRecyclerViewAdapter(MainActivity.this, WallItemList);
mRecyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(WallItem item) {
// Function to start new activity
Class detailActivity = DetailActivity.class;
// Create intent
Intent startDetailActivityIntent = new Intent(MainActivity.this, detailActivity);
// Add object to intent
startDetailActivityIntent.putExtra("detailWallItem", (Parcelable)item);
// Start activity
startActivity(startDetailActivityIntent);
}
});
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save instances of existing objects
outState.putString(JSON_KEY_RESULTS, JSONResults);
outState.putParcelableArrayList(WALL_ITEM_LIST_KEY, (ArrayList<? extends Parcelable>) this.WallItemList);
}
/**
* Parses JSON result
*
* #param result
*/
private void parseResult(String result) {
WallItemList = new ArrayList<>();
try {
JSONArray mJsonArray = new JSONArray(result);
// Loop through JSON array
for (int i = 0; i < mJsonArray.length(); i++) {
// Get picture URI fragment from JSON
String pictureURIFragment = mJsonArray.getJSONObject(i)
.getJSONArray("images").getJSONObject(0)
.getString("url");
// Load images into String
JSONArray JSONImageArray = mJsonArray.getJSONObject(i)
.getJSONArray("images");
// Create array for wallItem
String[] imageArray = new String[JSONImageArray.length()];
// Loop through JSONArray
for(int x = 0; x < JSONImageArray.length(); x++)
{
String pictureURLFragment = JSONImageArray.getJSONObject(x).getString("url");
// Built picture
URL pictureURL = NetworkUtils.builtPictureUrl(pictureURLFragment.toLowerCase());
imageArray[x] = java.net.URLDecoder.decode(pictureURL.toString());
}
// Built picture
URL pictureURL = NetworkUtils.builtPictureUrl(pictureURIFragment.toLowerCase());
String cleanPictureUrl = java.net.URLDecoder.decode(pictureURL.toString());
// add wall item to the list
WallItem item = new WallItem();
// Set fields of wallItem
item.setThumbnail(cleanPictureUrl);
item.setTitle(mJsonArray.getJSONObject(i).getString("author"));
item.setPhotographer(mJsonArray.getJSONObject(i).getString("photographer"));
item.setAddress(mJsonArray.getJSONObject(i).getString("address"));
item.setMaterial(mJsonArray.getJSONObject(i).getJSONObject("material").getString(langPreferenceString));
item.setDescription(mJsonArray.getJSONObject(i).getJSONObject("description").getString(langPreferenceString));
item.setImgURLArray(imageArray);
// Add wallItem to list
WallItemList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.api_url_settings_item)
{
Intent startSettingsActivity = new Intent(this, SettingsActivity.class);
startActivity(startSettingsActivity);
return true;
}
return super.onOptionsItemSelected(item);
}
private void getDeviceLanguage()
{
Log.d("HERE", Locale.getDefault().getLanguage());
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals(getString(R.string.pref_api_url_key)))
{
// Update String again
APIUrlPreferenceString = sharedPreferences.getString(getString(R.string.pref_api_url_key), getString(R.string.pref_api_url_def_value));
new DownloadTask().execute();
}
if(key.equals(getString(R.string.pref_lang_check_key)))
{
// 1. If true, use system language.
// 2. if System language != en or nl, use default language: en.
// 3. if false, make selectable
}
if(key.equals(getString(R.string.pref_lang_list_key)) || key.equals(getString(R.string.pref_lang_check_key)))
{
// Language settings
if(sharedPreferences.getBoolean(getString(R.string.pref_lang_check_key), true))
{
// Use device settings
setLanguageSettings(Resources.getSystem().getConfiguration().locale.getLanguage());
langPreferenceString = Resources.getSystem().getConfiguration().locale.getLanguage();
}
else
{
// Use preference settings
setLanguageSettings(sharedPreferences.getString(getString(R.string.pref_lang_list_key), getString(R.string.pref_lang_label_en)));
langPreferenceString = sharedPreferences.getString(getString(R.string.pref_lang_list_key), getString(R.string.pref_lang_label_en));
}
// Reload data after executing new Download task
new DownloadTask().execute();
this.recreate();
}
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
#Override
protected void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
}
}
Here is my MyRecyclerViewAdapter.java
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.CustomViewHolder> {
private List<WallItem> wallItemList;
private Context mContext;
private OnItemClickListener onItemClickListener;
public MyRecyclerViewAdapter(Context context, List<WallItem> wallItemList) {
this.wallItemList = wallItemList;
this.mContext = context;
WallItem w = wallItemList.get(0);
}
#Override
public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, null);
CustomViewHolder viewHolder = new CustomViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(CustomViewHolder customViewHolder, int i) {
final WallItem wallItem = wallItemList.get(i);
//Download image using picasso library
if (!TextUtils.isEmpty(wallItem.getThumbnail())) {
// Load image into imageView
Picasso.with(mContext).load(wallItem.getThumbnail())
.error(R.drawable.placeholder)
.placeholder(R.drawable.placeholder)
.into(customViewHolder.imageView);
}
//Setting text view title
customViewHolder.textView.setText(Html.fromHtml(wallItem.getMaterial()));
// Set OnClickListener to wallItem
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemClickListener.onItemClick(wallItem);
}
};
customViewHolder.imageView.setOnClickListener(listener);
customViewHolder.textView.setOnClickListener(listener);
}
// Overwrite to return
#Override
public int getItemCount() {
return (null != wallItemList ? wallItemList.size() : 0);
}
class CustomViewHolder extends RecyclerView.ViewHolder {
protected ImageView imageView;
protected TextView textView;
public CustomViewHolder(View view) {
super(view);
this.imageView = (ImageView) view.findViewById(R.id.thumbnail);
this.textView = (TextView) view.findViewById(R.id.title);
}
}
public OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}
My apologies for posting all the code but I can't identify the crucial points and don't have enough experience to pinpoint where it's going wrong. If anyone could help you would it would be greatly appreciated!
I suggest you to initialize and set the adapter in onCreate() method with an empty array of WallItems.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyRecyclerViewAdapter(MainActivity.this, new ArrayList<WallItem>());
mRecyclerView.setAdapter(adapter);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
// Setup shared preferences
setupSharedPreferences();
// Load the recyclerView
loadRecyclerView(savedInstanceState);
}
To update the list of items, I normally have a setItems method inside my adapter that updates the list and calls notifyDataSetChanged()
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.CustomViewHolder> {
...
public void setItems(List<WallItem> items) {
this.wallItemList = wallItemList;
notifyDataSetChanged();
}
}
Your populateRecyclerView method then should call the setItems method to update the new list of items.
private void populateRecyclerView()
{
WallItem w = WallItemList.get(0);
adapter.setItems(WallItemList);
adapter.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(WallItem item) {
// Function to start new activity
Class detailActivity = DetailActivity.class;
// Create intent
Intent startDetailActivityIntent = new Intent(MainActivity.this, detailActivity);
// Add object to intent
startDetailActivityIntent.putExtra("detailWallItem", (Parcelable)item);
// Start activity
startActivity(startDetailActivityIntent);
}
});
}
I didn't test, buy this is how I normally use RecyclerView.

How to continuously increment multiple RecyclerView values in parallel

Context
Working with RecyclerView items that each have label and value TextViews, and a start Button.
Goals
to start and continue incrementing a value when its start Button is pressed
must be possible to individually start each item's value via its Button
once started, multiple values must be able continuously increment in parallel
Problem
UI (value TextView, clicking Button, etc.) slows down after 2 or more values start incrementing. Unsure how to handle threading (tried HandlerThread/runOnUiThread, currently using individual thread for each MyValue object), see MyValue.start() in Code below.
How can individual values be continously incremented in parallel while updating RecyclerView without slowing UI?
What's been tried so far
Using HandlerThread and runOnUiThread
Using individual Thread for each MyValue object
Considered AsyncTask but in the future it is likely that "incrementing multiple values" behaviour will need to run in the background independent of the Activity (i.e., only updating GUI when Activity is in foreground), hence the behaviour may move to a BoundService
It may be possible to use LiveData to have RecyclerView items observe and update based on the value in MyValue, however, unsure how LiveData can work with MyValue and ValuesAdapter
Code
MainActivity
public class MainActivity extends AppCompatActivity {
ArrayList<MyValue> mValues;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
mValues = new ArrayList<>();
mValues.add(new MyValue("value 1"));
mValues.add(new MyValue("value 2"));
mValues.add(new MyValue("value 3"));
ValuesAdapter valuesAdapter = new ValuesAdapter(mValues);
recyclerView.setAdapter(valuesAdapter);
}
}
ValuesAdapter
public class ValuesAdapter extends RecyclerView.Adapter<ValuesAdapter.ValueViewHolder> {
private List<MyValue> mValues;
private Context mContext;
private ValueController mValueController;
public ValuesAdapter(List<MyValue> values) {
mValues = values;
mValueController = new ValueController(mValues);
}
#Override
public ValueViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mContext == null) {
mContext = parent.getContext();
}
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
View valueView = layoutInflater.inflate(R.layout.value_item, parent, false);
return new ValueViewHolder(valueView);
}
#Override
public void onBindViewHolder(ValueViewHolder holder, int position) {
MyValue value = mValues.get(position);
String label = value.getLabel();
int currentValue = value.getValue();
holder.setupViewHolder(label, "" + currentValue);
value.setListener(holder);
}
#Override
public int getItemCount() {
return mValues.size();
}
class ValueViewHolder extends RecyclerView.ViewHolder
implements MyValue.ValueListener {
TextView mLabel;
TextView mValue;
Button mStart;
public ValueViewHolder(View itemView) {
super(itemView);
mLabel = itemView.findViewById(R.id.label);
mValue = itemView.findViewById(R.id.value);
mStart = itemView.findViewById(R.id.start);
}
public void setupViewHolder(String label, String currentValue) {
mLabel.setText(label);
mValue.setText(currentValue);
int adapterPosition = getAdapterPosition();
final MyValue value = mValues.get(adapterPosition);
mStart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
value.start();
}
});
}
#Override
public void onTick(final int currentValue) {
mValueController.incActiveStopwatches();
((Activity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
mValue.setText("" + currentValue);
}
});
}
}
}
ValueController
public class ValueController {
private List<MyValue> mValues;
public ValueController(List<MyValue> values) {
mValues = values;
}
public void incrementActiveValues() {
for (int i = 0; i < mValues.size(); i++) {
MyValue value = mValues.get(i);
if (value.getShouldIncrement()) {
value.increment();
}
}
}
}
MyValue
public class MyValue {
private String mLabel;
private int mCurrentValue;
private boolean mShouldIncrement;
private Handler mHandler;
private ValueListener mListener;
public MyValue(String label) {
mLabel = label;
HandlerThread handlerThread = new HandlerThread("HandlerThread1");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
}
public String getLabel() {
return mLabel;
}
public int getValue() {
return mCurrentValue;
}
public void increment() {
mCurrentValue++;
}
public void start() {
mShouldIncrement = true;
new Thread(new Runnable() {
#Override
public void run() {
while (mShouldIncrement) {
increment();
if (mListener != null) {
mListener.onTick(mCurrentValue);
}
}
}
}).start();
// mHandler.post(new Runnable() {
// #Override
// public void run() {
// while (mShouldIncrement) {
// increment();
// if (mListener != null) {
// mListener.onTick(mCurrentValue);
// }
// }
// }
// });
}
public void setListener(ValueListener listener) {
mListener = listener;
}
public interface ValueListener {
void onTick(int currentValue);
}
public boolean getShouldIncrement() {
return mShouldIncrement;
}
}
Problem
Seems a high frequency of work (i.e., skipping around 2000 frames when incrementing value and updating RecyclerView TextView value) was done via MyValue.ValueListener.onTick().
Solution
Current solution was delay onTick to only fire every 1 second, i.e., replace MyValue.start() with this:
private Runnable mRepeatIncrementRunnable = new Runnable() {
#Override
public void run() {
if (mShouldIncrement) {
increment();
if (mListener != null) {
mListener.onTick(mCurrentValue);
}
} else {
mHandler.removeCallbacks(this);
return;
}
mHandler.postDelayed(this, mTickIntervalMs);
}
};
public void start() {
mShouldIncrement = true;
mHandler.post(mRepeatIncrementRunnable);
}

recyclerview not updating after notifydatasetchanged

I have chat fragment which is backed by recyclerview. When i enter chat screen manually to send or receive message its work fine. but when i closed app and enter app from message notification, Its sending message to other user but not updating own recyclerview.
Chat Fragment
public class Chat_Screen_Fragment extends Fragment implements View.OnClickListener, ChildEventListener{
public static final String TAG = "###CHAT SCREEN###";
List<Chat_Wrapper> message = new ArrayList<>();
Chat_Adapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.chat_screen_main_fragment,container,false);
setRetainInstance(true);
// GET INTENT VALUES FROM USER PROFILE CLASS
UserName_Intent = getArguments().getString("Get_Name");
UserImage_Intent = getArguments().getString("Get_Image");
UserPhone_Intent = getArguments().getString("Get_Phone");
UserID_Intent = getArguments().getString("Get_ID");
FirebaseToken_Intent = getArguments().getString("Get_Token"); //Firebase Token of other person
Room_Name_Intent = getArguments().getString("Get_Other"); // Room Name of chat
UserLastSeen_Intent=getArguments().getString("LastSeen");
//Sender_FCMToken = Session.getFirebaseID();
// RECYCLER VIEW
recyclerView = v.findViewById(R.id.Chat_Screen_Message_List);
layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setStackFromEnd(true);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
databaseReference = FirebaseDatabase.getInstance().getReference().child(Room_Name_Intent);
databaseReference.addChildEventListener(this);
adapter = new Chat_Adapter(getActivity(), message);
recyclerView.setAdapter(adapter);
// FETCH OLD MESSAGE FROM DATABASE
chatDatabase();
return v;
}
// FIREBASE REAL TIME DATABASE WHICH FETCH ALL MESSAGES (SYNC) FROM ONLINE DATABASE
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
//THIS METHOD WILL FETCH ADD ALL MESSAGES FROM FIREBASE DATABASE AND ALSO SEARCH RESULT
private synchronized void append_chat_conversation(DataSnapshot dataSnapshot) {
iterator = dataSnapshot.getChildren().iterator();
while (iterator.hasNext()) {
// NOW GET ALL DATA FROM FIREBASE DATABASE AND SAVE IT INTO STRINGS THEN CHECK EACH BY ITS MESSAGE TYPE
Chat_Msg = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FROM = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FCM_TO= (String) ((DataSnapshot) iterator.next()).getValue();
Chat_Database tempChatDatabase = new Chat_Database(getActivity());
boolean hasValue=tempChatDatabase.CheckValueExist(_ID);
if (!hasValue) {
Log.d(TAG,"Chat Message "+Chat_Msg);
long id=chat_database.Insert_Chat(Session.getUserID(),Room_Name_Intent, UserID_Intent, "Text", Chat_Msg, Chat_FROM, Chat_TO, Chat_TimeStamp, Chat_FCM_FROM, Chat_FCM_TO, Session.getPhoneNO(), UserPhone_Intent,Random_ID,UserImage_Intent,UserLastSeen_Intent,Chat_FROM_ID);
//Adding Chat Data Into Database
Log.d(TAG,"Database Entry ID "+id);
if (id==0){
Log.d(TAG,"Database Already Has Value Of This Random Id ");
return;
}
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, User_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
adapter.notifyDataSetChanged();
Log.d(TAG, "FIREBASE STORAGE PHOTO-3-MESSAGE ARRAY SIZE " + message.size());
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
}
}
Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
tempChatDatabase.isDatabaseClose();
}
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
//recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
private void chatDatabase(){
//Database Init and Filling Adapter
Log.d(TAG,"Chat Database Function");
chat_database=new Chat_Database(getActivity());
chatCursor=chat_database.getUserChat(UserID_Intent);
boolean checkDB_Exist=functions.DatabaseExist(getActivity(),"CHAT_DATABASE.DB");
boolean chatItemsCounts=chatCursor.getCount()>0;
chatCursor.moveToFirst();
Log.d(TAG,"Value At Chat Database "+ checkDB_Exist+" "+chatItemsCounts);
if (checkDB_Exist && chatCursor.getCount()>0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)){
Log.d(TAG,"Database Exist Chat Database");
message.clear();
chatCursor.moveToFirst();
do {
database_rowID=chatCursor.getInt(chatCursor.getColumnIndex("ID"));
database_userID=chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
database_RoomName =chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
database_ReceiverID=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
database_MessageType=chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
database_Message=chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
database_MsgFrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
database_MsgTo=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
database_TimeStamp=chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
database_FCMfrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
database_FCMto=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
database_LocalPath=chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
database_PhoneFrom=chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
database_PhoneTo=chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
Log.d(TAG,"Value Of Database Message String = "+database_Message);
Log.d(TAG,"Row ID of Database "+database_rowID);
// Check Message Type
Log.d(TAG,"Message Type Is Text");
Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom,null,null,database_rowID);
message.add(text);
}
while(chatCursor.moveToNext());
Room_Name_Intent = database_RoomName;
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter.notifyDataSetChanged();
chatCursor.close();
boolean value = chat_database.isDatabaseClose();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
}
});
Log.d(TAG,"Value Of Database Close or Not "+value);
}
}
}
Chat Adapter
public class Chat_Adapter extends RecyclerView.Adapter<Chat_Adapter.ViewHolder> {
public static final String TAG="###CHAT_ADAPTER###";
private Context context;
Chat_Database database;
Chat_Wrapper chat_wrapper;
public Chat_Adapter(Context context, List<Chat_Wrapper> message) {
this.context = context;
this.arrayList_message = message;
}
#Override
public Chat_Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View Layout;
Log.d(TAG,"On Create View Holder Calling ");
if (viewType==1){
Log.d(TAG,"View Tyoe Is "+viewType);
Layout=LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_screen_message_item,parent,false);
// ImagePath=Session.getUserImage();
}
else {
Log.d(TAG,"View Type Is "+viewType);
Layout=LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_screen_message_item_other,parent,false);
// ImagePath=chat_wrapper.getImageView();
}
return new ViewHolder(Layout);
}
#Override
public void onBindViewHolder(final Chat_Adapter.ViewHolder holder, final int position) {
chat_wrapper=arrayList_message.get(position);
database=new Chat_Database(context);
holder.Message.setVisibility(View.VISIBLE);
holder.TimeStamp.setVisibility(View.VISIBLE);
holder.User_Image.setVisibility(View.VISIBLE);
//CHECK SENDER IS SAME AS LOGGED IN USER
if ((Session.getUserFname()+" "+Session.getUserLname()).equals(chat_wrapper.getSender_UserName())){
ImagePath=Session.getUserImage();
Log.d(TAG,"Session.getUserImage() "+Session.getUserImage());
Log.d(TAG,"Value Of Message Running ImagePath "+ImagePath);
}
else {
//String filePath="/data/data/com.boysjoys.com.pro_working1/my_picture.jpg"
ImagePath=chat_wrapper.getImageView();
Log.d(TAG,"Value Of Message Running ImagePath "+ImagePath);
}
holder.Message.setText(chat_wrapper.getMessage());
holder.TimeStamp.setText(chat_wrapper.getTimestamp());
holder.TimeStamp.setVisibility(View.GONE);
Glide.with(MyApplication.getmContext())
.load(ImagePath)
.apply(new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.fitCenter().skipMemoryCache(false))
.into(holder.User_Image);
//Make TimeStamp Visible or Hidden
holder.Message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d(TAG,"On Click Text View Message");
if (isShowing){
holder.TimeStamp.setVisibility(View.VISIBLE);
Log.d(TAG,"Is Showing Is True");
isShowing=false;
}
else {
holder.TimeStamp.setVisibility(View.GONE);
isShowing=true;
}
}
});
}
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemCount() {
Log.d(TAG,"GET ITEM COUNT--Array Message List Size "+arrayList_message.size());
return arrayList_message.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView Message;
TextView TimeStamp;
ImageView User_Image;
public ViewHolder(View itemView) {
super(itemView);
Log.d(TAG,"View Holder Constructor Calling. Now Inflating Layout Items");
Message = itemView.findViewById(R.id.Single_Item_Chat_Message);
TimeStamp = itemView.findViewById(R.id.Single_Item_Chat_TimeStamp);
User_Image = itemView.findViewById(R.id.Single_Item_Chat_ImageView);
}
}
Whenever i received message as notification after tapping on notification it open same chat screen (Load all previous message first) and when i click on send button to send message it send to other user but not updating my recyclerview.
UPDATE: I just found the issue which is fetching earlier messages from local database when i comment chatDatabase() function it works fine. but still not able to fix the issue.
When you are appending a new item to the list in append_chat_conversation method, you are not notifying the adapter about it. I don't know how that framework works but you should notify the adapter about any change in the dataset by using the notifyDataSetChaged on the adapter or better using the specific inserted/changed/deleted notify methods for better performance.
Why don't you call
adapter.notifyDataSetChanged();
after
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, User_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
as
notifyDataSetChanged only works if you use the add(), insert(), remove(), and clear() on the Adapter.

change item from RecyclerView out of onBindViewHolder

On my onBindViewHolder I have this to set the setImageResource
holder.card_image.setImageResource(image);
But my items can be purchased so, I have this to purchase on my holder.view.setOnClickListener()
bp.purchase((Activity) mContext,model.getProduct_id());
so, it goes to this method :
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
//WANT TO CHANGE THE IMAGE ONCE PURCHASE IS OK
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
How do I change it if I'm not onBindViewHolder?
My adapter looks like :
FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter< CardPOJO, CardHolder>(options) {
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//inflate the single recycler view layout(item)
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_product, parent, false);
int width = parent.getMeasuredWidth() / 2;
width -= mContext.getResources().getDimensionPixelSize(R.dimen._8sdp);
final CardHolder cardViewHolder = new CardHolder(view,width);
return cardViewHolder;
}
#Override
public void onDataChanged() {
super.onDataChanged();
tv.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
#Override
protected void onBindViewHolder(CardHolder holder, int position, final CardPOJO model) {
holder.state.setText(model.getState());
holder.cardName.setText(model.getName());
switch (model.getState()){
case "free":
//Img free
break;
case "not_free":
//Img not free
break;
default:
break;
}
holder.view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(model.getState().equals("free")){
//stuff
}
else{
//stuff
}
root_ref.child("PurchasedProducts").child(currentuser).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bp.purchase((Activity) mContext,model.getProduct_id()); //HERE I CALL THE PURCHASE SO IF IT'S OK I WANT TO DO SOMETHING LIKE holder.card_image.setImageResource(image);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
});
}
};
adapter.startListening();
products_recycler.setAdapter(adapter);
If I assume correctly you want to change the view appearance or some image change if some payment is done successful or failed.
for that, you can have a callback which will give you the item position in activity or fragment back from there you can make a server call to make the purchase happen and if everything goes well.
when you make your adapter constructor pass the callback
final SomeAdapter obj = new SomeAdapter(this,new Callback(){
#Override
onPaymentRequested(int position, View view){
//this will get called when you press click on image in bindviewholder
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
adapterModelList.get(position).setPayment(true);
obj.notifyDataSetChanged();
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
}
});
recyclerView.setAdapter(obj);
so when you call your obj.notifyDataSetChanged(); it will make the adapter to draw all views again where you can set some flag according to int position recieved for click callback and make it change accordingly.
Edit=>07/12/2018: Tried the Firebase Adapter and made few changes since the code was not enough to replicate the scenario but I have made a sample class made few changes but the basic idea is like below.
1: When user click on view in onBindViewHolder we receive a callback which gives a position parameter in fragment or activity from where we are calling
2: Now we process the payment and when we are done we make a change in Database firebase also by updating the CardPojo to server for that particular user item.
3: while we update the CardPojo on server we also set a flag in card pojo which is a boolean for paymentSuccess which will be true when payment is done.
4: since our payment is done and is synced with server with new flag data now we can just call firebaseRecycler.notifyItemChanged(position); which will get the lates update from the server for that particular position which we have received on callback.
5: Now populateViewHolder() gives you a cardpojo object you can check if payment is done then you can change the image
so here is the sample code involved I have tried to match the scenario at best, hope you understand what I am trying to do here.
so first create a listener or a callback
public interface CallBackInterface {
void onClick(int position,CardPOJO cardPOJO);
}
now instead of initializing the FirebaseRecyclerAdapter in activity or fragment just create a class and extend it this separates your ui logic and gives us the extensibility of doing extra things like adding callback.
public class FirebaseRecycler extends FirebaseRecyclerAdapter<CardPOJO,CardHolder> {
CallBackInterface callBackInterface;
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, DatabaseReference ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, Query ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//your inflater logic goes here
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_product, parent, false);
CardHolder cardHolder = new CardHolder(view);
return cardHolder;
}
#Override
protected void populateViewHolder(CardHolder viewHolder, final CardPOJO model, final int position) {
//your populate logic
//your existing code here
if (model.isPaymentDone){
//set payment success image holder.card_image.setImageResource(image);
}else{
//set payment failure image
}
//setting the card click listener
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//we have the card click listener, we will start the payment processing in activity
callBackInterface.onClick(position,model);
}
});
}
public void setCallBackInterface(CallBackInterface callBackInterface) {
this.callBackInterface = callBackInterface;
}
}
now almost everything is done we need to call this Custom Firebase adapter and pass the required things and it will do its job.
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final DatabaseReference mDatabaseRef = FirebaseDatabase.getInstance().getReference();
/*
if you have any other database child then you can refer to it using
DatabaseReference child = mDatabaseRef.child("yourchilddatabase");
and pass this to the last argument
*/
final FirebaseRecycler firebaseRecycler = new FirebaseRecycler(CardPOJO.class, R.layout.card_product, CardHolder.class, mDatabaseRef);
firebaseRecycler.setCallBackInterface(new CallBackInterface() {
#Override
public void onClick(final int position, final CardPOJO cardPOJO) {
//start processing the payment
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
/**
*when you have processed the payment just enable the flag on server database by having a extra boolean flag for this
* and check in onBindViewHolder if this is enabled if so then replace your image
* updating the values on server, you can handle it according to your user case
*/
cardPOJO.setPaymentDone(true);
mDatabaseRef.push().setValue(cardPOJO);
firebaseRecycler.notifyItemChanged(position);
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
//existing logic
}
#Override
public void onBillingInitialized() {
//existing logic
}
#Override
public void onPurchaseHistoryRestored() {
//existing logic
}
};
}
});
}
this demonstrates the basic logic you can patch it according to your requirement.
Get your item from RecyclerView's adapter and edit it. Then just call Adapter.onItemChanged(int position), this will cause to call onBindViewholder to be called specifically for that position.

CheckBox state inside RecyclerView

I created a Checkbox, however, whenever I click a few Items random checkboxes become clicked. I have a recycler view, and I have an adapter class as well. Here is my CheckBox method can anyone tell me the problem?
public void CheckBox(View view) {
final CheckBox checkBox = (CheckBox)view;
if (checkBox.isChecked()) {
System.out.println("SET TO CHECKED");
//Input instance of selected course(CHECKED)
// TODO: 2/5/16 Need to pass in Persisted ID
mVolleySingleton = VolleySingleton.getInstance();
mRequestQueue = mVolleySingleton.getRequestQueue();
CharSequence ID_PUT_COURSES = ((TextView) ((RelativeLayout) view.getParent()).getChildAt(1)).getText();
System.out.println(PUT);
String URL_PUT_COURSES = "URL"+ID_PUT+"\n";
System.out.print(PUT);
StringRequest UrlPut = new StringRequest(Request.Method.PUT, URL_PUT, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
System.out.println(response + "reponse");
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
System.out.println("************Answer" + error + "error");
}
});
mRequestQueue.add(UrlPutCourses);
System.out.println("Added");
}
else{
System.out.println("SET TO UNCHECKED");
//delete instance of selected course(UNCHECKED)
mVolleySingleton = VolleySingleton.getInstance();
mRequestQueue = mVolleySingleton.getRequestQueue();
// TODO: 2/5/16 Need to pass in Persisted ID
CharSequence DELETE = ((TextView) ((RelativeLayout) view.getParent()).getChildAt(1)).getText();
System.out.print(ID_PUT_COURSES);
String UR_DELETE = "URL"+ DELETE;
StringRequest UrlDeleteCourses = new StringRequest(Request.Method.DELETE, UR_DELETE, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
System.out.println(response + "reponse");
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
System.out.println("************Answer" + error + "error");
}
});
mRequestQueue.add(UR_DELETE);
System.out.println("Deleted");
}
}
ublic class AdapterSearch extends RecyclerView.Adapter<AdapterSearch.ViewSearch>{
private LayoutInflater mLayoutInflater;
private ArrayList<Search> ListSearch=new ArrayList<>();
public AdapterSearch(Context context){
mLayoutInflater=LayoutInflater.from(context);
}
public void setListSearch(ArrayList<Search> ListSearch){
this.ListSearch=ListSearch;
notifyItemRangeChanged(0,ListSearch.size());
}
#Override
public ViewSearch onCreateViewHolder(ViewGroup parent, int viewType) {
View view= mLayoutInflater.inflate(R.layout.custom_search,(ViewGroup)null);
ViewSearch holder=new ViewSearch(view);
return holder;
}
#Override
public void onBindViewHolder(ViewSearch holder, int position) {
Search currentSearch=ListSearch.get(position);
holder.mSearchText.setText(currentSearch.getMtitle());
holder.mAnswerPointsSearch.setText(currentSearch.getMkey());
holder.mSearchId.setText(currentSearch.getMid());
holder.mCourseId.setText(currentSearch.getCourseId());
}
#Override
public int getItemCount() {
return ListSearch.size();
}
public void setFilter(ArrayList<Search> Search) {
ListSearch = new ArrayList<>();
ListSearch.addAll(Search);
notifyDataSetChanged();
}
static class ViewSearch extends RecyclerView.ViewHolder{
private TextView mSearchText;
private TextView mAnswerPointsSearch;
private TextView mSearchId;
private TextView mCourseId;
public ViewSearch (View view){
super(view);
mSearchText=(TextView)itemView.findViewById(R.id.SearchText);
mAnswerPointsSearch=(TextView)itemView.findViewById(R.id.AnswerPointsSearch);
mSearchId=(TextView)itemView.findViewById(R.id.SearchId);
mCourseId=(TextView)itemView.findViewById(R.id.CourseTextView);
}
}
}
Better late then never I'd say, it might help other people stumbling upon this issue later, so here goes.
This might not be the best solution ever, but it should work.
In your Search class, you have some variables like title and key. Add the boolean isChecked to it, and make it false by default. Also create the matching getter and setter. (I'm using isChecked() & setChecked() in this example)
In your onBindViewHolder() method, add the following lines:
CheckBox checkBox = (CheckBox) itemView.findViewById(R.id.CheckBoxID);
// Instantiating the checkbox
holder.checkBox.setChecked(currentSearch.isChecked());
// Every time the holder (one list item) is shown, this gets called.
// It sets the checked state based on what's stored in currentSearch.
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (checkBox.isChecked()) {
currentSearch.setChecked(true)
} else {
currentSearch.setChecked(false);
}
});
}
// This listens for clicks on the checkbox, and changes the checked state of
// the boolean in currentSearch to the correct state based on
// what it already was on.
Please be aware that I haven't worked with checkboxes before, so some things (like setting the checked state) might be done slightly differently from what I've described above, but this should be a working base.
You also probably want the checkboxes to only persevere until you click a button or something like that, and then have them reset. You might want to add a simple for loop for that, something like:
public void resetAllCheckBoxes() {
for (Search s : ListSearch) {
s.setChecked(false);
}
}

Categories

Resources