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);
}
Related
I use this code to play the full-screen video but I have a problem when it is video playing and moving from the main activity to the full-screen activity occurs freezing of the video for 2-3 seconds This problem occurs only with the releases after 2.8.3 only but with 2.8.0 works Video is smooth
code full: https://github.com/MATPOCKIH/ExoPlayerFullscreen
PlayerViewManager
public class PlayerViewManager {
private static final String TAG = "ExoPlayerViewManager";
public static final String EXTRA_VIDEO_URI = "video_uri";
private static Map<String, PlayerViewManager> instances = new HashMap<>();
private Uri videoUri;
public boolean isPlayerPlaying;
private boolean isMustPlaying;
private UniversalPlayerView universalPlayer;
public static PlayerViewManager getInstance(String videoUri) {
PlayerViewManager instance = instances.get(videoUri);
if (instance == null) {
instance = new PlayerViewManager(videoUri);
instances.put(videoUri, instance);
}
return instance;
}
private PlayerViewManager(String videoUri) {
this.videoUri = Uri.parse(videoUri);
}
public void preparePlayer(PlayerHolderView playerHolderView) {
if (playerHolderView == null) {
return;
}
if (universalPlayer == null) {
universalPlayer = createPlayer(playerHolderView.getContext());
isPlayerPlaying = true;
isMustPlaying = true;
}
universalPlayer.initialize(videoUri, playerHolderView);
}
public void releaseVideoPlayer() {
if (universalPlayer != null) {
universalPlayer.release();
}
universalPlayer = null;
}
public void goToBackground() {
if (universalPlayer != null /*&& !isMustPlaying*/) {
//isPlayerPlaying = player.getPlayWhenReady();
universalPlayer.pause();
}
}
public void goToForeground() {
if (universalPlayer != null && isMustPlaying) {
universalPlayer.play();
}
}
public void pausePlayer(){
if (universalPlayer != null) {
universalPlayer.pause();
isPlayerPlaying = false;
isMustPlaying = false;
}
}
public void playPlayer(){
if (universalPlayer != null) {
universalPlayer.play();
isPlayerPlaying = true;
isMustPlaying = true;
}
}
private UniversalPlayerView createPlayer(Context context){
if (videoUri.getScheme().startsWith("http")){
return new FaceterExoPlayerView(context);
}
return new FaceterExoPlayerView(context);
}
}
FaceterExoPlayerView
public class FaceterExoPlayerView extends UniversalPlayerView {
private Uri videoUri;
private DefaultDataSourceFactory dataSourceFactory;
private SimpleExoPlayer player;
private PlayerView exoPlayerView;
private Context context;
public FaceterExoPlayerView(Context context) {
this.context = context;
}
#Override
public void initialize(Uri videoUri, PlayerHolderView playerHolderView) {
if (playerHolderView == null || videoUri == null)
return;
exoPlayerView = playerHolderView.findViewById(R.id.exo_player);
if (player == null) {
player = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());
dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, "faceter"));
MediaSource videoSource = buildMediaSource(videoUri, null);
player.prepare(videoSource);
}
player.clearVideoSurface();
player.setVideoTextureView((TextureView) exoPlayerView.getVideoSurfaceView());
exoPlayerView.setPlayer(player);
exoPlayerView.hideController();
setResizeModeFill(playerHolderView.isResizeModeFill());
}
#Override
public void play() {
player.setPlayWhenReady(true);
}
#Override
public void pause() {
player.setPlayWhenReady(false);
}
#SuppressWarnings("unchecked")
private MediaSource buildMediaSource(Uri uri, #Nullable String overrideExtension) {
int type = Util.inferContentType(uri, overrideExtension);
switch (type) {
/*case C.TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false))
.setManifestParser(
new FilteringManifestParser<>(
new DashManifestParser(), (List<RepresentationKey>) getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false))
.setManifestParser(
new FilteringManifestParser<>(
new SsManifestParser(), (List<StreamKey>) getOfflineStreamKeys(uri)))
.createMediaSource(uri);*/
case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory)
/*.setPlaylistParser(
new FilteringManifestParser<>(
new HlsPlaylistParser(), (List<RenditionKey>) getOfflineStreamKeys(uri)))*/
.createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
}
#Override
public void release() {
if (player != null) {
player.release();
}
player = null;
}
#Override
public void setResizeModeFill(boolean isResizeModeFill) {
if (isResizeModeFill) {
exoPlayerView.setResizeMode(RESIZE_MODE_FILL);
} else {
exoPlayerView.setResizeMode(RESIZE_MODE_FIT);
}
}
}
PlayerHolderView.java
public class PlayerHolderView extends FrameLayout {
private String videoUrl;
private boolean isResizeModeFill = true;
private OnUserInteractionListener onUserInteractionListener;
public PlayerHolderView(#NonNull Context context) {
super(context);
init();
}
public PlayerHolderView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public PlayerHolderView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.layout_player, this, true);
View controlView = this.findViewById(R.id.exo_controller);
controlView.findViewById(R.id.exo_play)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PlayerViewManager.getInstance(videoUrl).playPlayer();
}
});
controlView.findViewById(R.id.exo_pause)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PlayerViewManager.getInstance(videoUrl).pausePlayer();
}
});
controlView.findViewById(R.id.exo_fullscreen_button)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), FullscreenVideoActivity.class);
intent.putExtra(PlayerViewManager.EXTRA_VIDEO_URI, videoUrl);
getContext().startActivity(intent);
}
});
MainActivity
public class MainActivity extends AppCompatActivity {
private List<PlayerHolderView> playerHolders = new ArrayList<>();
private List<TextView> links = new ArrayList<>();
private List<String> mVideoUrls = new ArrayList<>(
Arrays.asList(
//"http://10.110.3.30/api/Playlists/6a3ecad7-e744-446f-9341-0e0ba834de63?from=2018-09-20&to=2018-09-21"
"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/TearsOfSteel.m3u8",
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube&sparams=ip,ipbits,expire,source,id&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=513F28C7FDCBEC60A66C86C9A393556C99DC47FB.04C88036EEE12565A1ED864A875A58F15D8B5300&key=ik0",
"https://html5demos.com/assets/dizzy.mp4"
//"https://cdn.faceter.io/hls/ab196789-8876-4854-82f3-087e5682d013",
//"https://cdn.faceter.io/hls/65d1c673-6a63-44c8-836b-132449c9462a"
)
);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
playerHolders.add((PlayerHolderView) findViewById(R.id.holder1));
playerHolders.add((PlayerHolderView) findViewById(R.id.holder2));
playerHolders.add((PlayerHolderView) findViewById(R.id.holder3));
links.add((TextView) findViewById(R.id.title1));
links.add((TextView) findViewById(R.id.title2));
links.add((TextView) findViewById(R.id.title3));
}
#Override
public void onResume() {
super.onResume();
int i = 0;
for (final String videoUrl : mVideoUrls) {
playerHolders.get(i).setupPlayerView(videoUrl);
playerHolders.get(i).setOnUserInteractionListener(this);
links.get(i).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onVideoTitleClicked(videoUrl);
}
});
i++;
}
}
#Override
public void onPause() {
super.onPause();
for (String videoUrl : mVideoUrls) {
PlayerViewManager.getInstance(videoUrl).goToBackground();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
for (String videoUrl : mVideoUrls) {
PlayerViewManager.getInstance(videoUrl).releaseVideoPlayer();
}
}
public void onVideoTitleClicked(String videoUrl) {
Intent intent = new Intent(getBaseContext(), DetailActivity.class);
intent.putExtra(PlayerViewManager.EXTRA_VIDEO_URI, videoUrl);
startActivity(intent);
}
}
FullscreenVideoActivity
public class FullscreenVideoActivity extends AppCompatActivity {
/**
* Some older devices needs a small delay between UI widget updates
* and a change of the status and navigation bar.
*/
private static final int UI_ANIMATION_DELAY = 300;
private final Handler mHideHandler = new Handler();
private View mContentView;
private final Runnable mHidePart2Runnable = new Runnable() {
#SuppressLint("InlinedApi")
#Override
public void run() {
// Delayed removal of status and navigation bar
// Note that some of these constants are new as of
// API 19 (KitKat). It is safe to use them, as they are inlined
// at compile-time and do nothing on earlier devices.
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
};
private final Runnable mHideRunnable = new Runnable() {
#Override
public void run() {
hide();
}
};
private String mVideoUri;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen_video);
mContentView = findViewById(R.id.enclosing_layout);
PlayerHolderView playerHolderView = findViewById(R.id.player_holder);
playerHolderView.setResizeModeFill(false);
mVideoUri = getIntent().getStringExtra(PlayerViewManager.EXTRA_VIDEO_URI);
PlayerViewManager.getInstance(mVideoUri).preparePlayer(playerHolderView);
/*
// Set the fullscreen button to "close fullscreen" icon
View controlView = playerView.findViewById(R.id.exo_controller);
ImageView fullscreenIcon = controlView.findViewById(R.id.exo_fullscreen_icon);
fullscreenIcon.setImageResource(R.drawable.exo_controls_fullscreen_exit);
controlView.findViewById(R.id.exo_fullscreen_button)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
controlView.findViewById(R.id.exo_play)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PlayerViewManager.getInstance(mVideoUri).playPlayer();
}
});
controlView.findViewById(R.id.exo_pause)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PlayerViewManager.getInstance(mVideoUri).pausePlayer();
}
});*/
}
#Override
public void onResume() {
super.onResume();
PlayerViewManager.getInstance(mVideoUri).goToForeground();
}
#Override
public void onPause() {
super.onPause();
PlayerViewManager.getInstance(mVideoUri).goToBackground();
}
#Override
public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide();
}
private void hide() {
// Schedule a runnable to remove the status and navigation bar after a delay
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
}
/**
* Schedules a call to hide() in delay milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide() {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, 100);
}
}
Just try out this code for video freeze problem:
#Override
public void play() {
player.setPlayWhenReady(true);
player.getPlaybackState();
}
#Override
public void pause() {
player.setPlayWhenReady(false);
player.getPlaybackState();
}
Here player.getPlaybackState(); is help full to get it to back state.
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.
I'm loading json file from online and saving it on Sqlite such that when app is offline...Then still user will be able to see the data.
It works fine in MainActivity.
But when I try to covert it into fragment, I'm getting errors of Fragment cannot be cast in to FlowerAdapter$FlowerClickListener
Here is the error file
My Fragment name is nepali.
Here is the Mainactivity
public class MainActivity extends AppCompatActivity implements FlowerAdapter.FlowerClickListener, FlowerFetchListener {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private RestManager mManager;
private FlowerAdapter mFlowerAdapter;
private FlowerDatabase mDatabase;
private Button mReload;
private ProgressDialog mDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
configViews();
mManager = new RestManager();
mDatabase = new FlowerDatabase(this);
loadFlowerFeed();
mReload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
loadFlowerFeed();
}
});
}
private void loadFlowerFeed() {
mDialog = new ProgressDialog(MainActivity.this);
mDialog.setMessage("Loading Flower Data...");
mDialog.setCancelable(true);
mDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mDialog.setIndeterminate(true);
mFlowerAdapter.reset();
mDialog.show();
if (getNetworkAvailability()) {
getFeed();
} else {
getFeedFromDatabase();
}
}
private void getFeedFromDatabase() {
mDatabase.fetchFlowers(this);
}
private void configViews() {
mReload = (Button) findViewById(R.id.reload);
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false));
mFlowerAdapter = new FlowerAdapter(this);
mRecyclerView.setAdapter(mFlowerAdapter);
}
#Override
public void onClick(int position) {
}
public void getFeed() {
Call<List<Flower>> listCall = mManager.getFlowerService().getAllFlowers();
listCall.enqueue(new Callback<List<Flower>>() {
#Override
public void onResponse(Call<List<Flower>> call, Response<List<Flower>> response) {
if (response.isSuccessful()) {
List<Flower> flowerList = response.body();
for (int i = 0; i < flowerList.size(); i++) {
Flower flower = flowerList.get(i);
SaveIntoDatabase task = new SaveIntoDatabase();
task.execute(flower);
mFlowerAdapter.addFlower(flower);
}
} else {
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
mDialog.dismiss();
}
#Override
public void onFailure(Call<List<Flower>> call, Throwable t) {
mDialog.dismiss();
Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public boolean getNetworkAvailability() {
return Utils.isNetworkAvailable(getApplicationContext());
}
#Override
public void onDeliverAllFlowers(List<Flower> flowers) {
}
#Override
public void onDeliverFlower(Flower flower) {
mFlowerAdapter.addFlower(flower);
}
#Override
public void onHideDialog() {
mDialog.dismiss();
}
public class SaveIntoDatabase extends AsyncTask<Flower, Void, Void> {
private final String TAG = SaveIntoDatabase.class.getSimpleName();
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Flower... params) {
Flower flower = params[0];
try {
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return null;
}
}
}
and FlowerDatabase class is
public class FlowerDatabase extends SQLiteOpenHelper {
private static final String TAG = FlowerDatabase.class.getSimpleName();
public FlowerDatabase(Context context) {
super(context, Constants.DATABASE.DB_NAME, null, Constants.DATABASE.DB_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(Constants.DATABASE.CREATE_TABLE_QUERY);
} catch (SQLException ex) {
Log.d(TAG, ex.getMessage());
}
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(Constants.DATABASE.DROP_QUERY);
this.onCreate(db);
}
public void addFlower(Flower flower) {
Log.d(TAG, "Values Got " + flower.getName());
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(Constants.DATABASE.PRODUCT_ID, flower.getProductId());
values.put(Constants.DATABASE.CATEGORY, flower.getCategory());
values.put(Constants.DATABASE.PRICE, Double.toString(flower.getPrice()));
values.put(Constants.DATABASE.INSTRUCTIONS, flower.getInstructions());
values.put(Constants.DATABASE.NAME, flower.getName());
try {
db.insert(Constants.DATABASE.TABLE_NAME, null, values);
} catch (Exception e) {
}
db.close();
}
public void fetchFlowers(FlowerFetchListener listener) {
FlowerFetcher fetcher = new FlowerFetcher(listener, this.getWritableDatabase());
fetcher.start();
}
public class FlowerFetcher extends Thread {
private final FlowerFetchListener mListener;
private final SQLiteDatabase mDb;
public FlowerFetcher(FlowerFetchListener listener, SQLiteDatabase db) {
mListener = listener;
mDb = db;
}
#Override
public void run() {
Cursor cursor = mDb.rawQuery(Constants.DATABASE.GET_FLOWERS_QUERY, null);
final List<Flower> flowerList = new ArrayList<>();
if (cursor.getCount() > 0) {
if (cursor.moveToFirst()) {
do {
Flower flower = new Flower();
flower.setFromDatabase(true);
flower.setName(cursor.getString(cursor.getColumnIndex(Constants.DATABASE.NAME)));
flower.setPrice(Double.parseDouble(cursor.getString(cursor.getColumnIndex(Constants.DATABASE.PRICE))));
flower.setInstructions(cursor.getString(cursor.getColumnIndex(Constants.DATABASE.INSTRUCTIONS)));
flower.setCategory(cursor.getString(cursor.getColumnIndex(Constants.DATABASE.CATEGORY)));
flower.setProductId(Integer.parseInt(cursor.getString(cursor.getColumnIndex(Constants.DATABASE.PRODUCT_ID))));
flowerList.add(flower);
publishFlower(flower);
} while (cursor.moveToNext());
}
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
mListener.onDeliverAllFlowers(flowerList);
mListener.onHideDialog();
}
});
}
public void publishFlower(final Flower flower) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
mListener.onDeliverFlower(flower);
}
});
}
}
}
and FlowerAdapter Class is
public class FlowerAdapter extends RecyclerView.Adapter<FlowerAdapter.Holder> {
private static final String TAG = FlowerAdapter.class.getSimpleName();
private final FlowerClickListener mListener;
private List<Flower> mFlowers;
public FlowerAdapter(FlowerClickListener listener) {
mFlowers = new ArrayList<>();
mListener = listener;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, null, false);
return new Holder(row);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
Flower currFlower = mFlowers.get(position);
holder.mName.setText(currFlower.getName());
holder.minstruction.setText(currFlower.getInstructions());
}
#Override
public int getItemCount() {
return mFlowers.size();
}
public void addFlower(Flower flower) {
mFlowers.add(flower);
notifyDataSetChanged();
}
/**
* #param position
* #return
*/
public Flower getSelectedFlower(int position) {
return mFlowers.get(position);
}
public void reset() {
mFlowers.clear();
notifyDataSetChanged();
}
public class Holder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mName, minstruction;
public Holder(View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.flowerName);
minstruction = (TextView) itemView.findViewById(R.id.flowerPrice);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
mListener.onClick(getLayoutPosition());
}
}
public interface FlowerClickListener {
void onClick(int position);
}
}
My fragment class is that I try to convert above Mainactivity code into Frament class is
public class nepali extends Fragment {
private static final String TAG = nepali.class.getSimpleName();
private RecyclerView mRecyclerView;
private RestManager mManager;
private FlowerAdapter mFlowerAdapter;
private FlowerDatabase mDatabase;
private Button mReload;
private ProgressDialog mDialog;
View view;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.activity_data, container, false);
configViews();
mManager = new RestManager();
mDatabase = new FlowerDatabase(getActivity());
loadFlowerFeed();
mReload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
loadFlowerFeed();
}
});
return view;
}
private void loadFlowerFeed() {
mDialog = new ProgressDialog(getActivity());
mDialog.setMessage("Loading Flower Data...");
mDialog.setCancelable(true);
mDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mDialog.setIndeterminate(true);
mFlowerAdapter.reset();
mDialog.show();
if (getNetworkAvailability()) {
getFeed();
} else {
getFeedFromDatabase();
}
}
private void getFeedFromDatabase() {
mDatabase.fetchFlowers(this);
}
private void configViews() {
mReload = (Button) view.findViewById(R.id.reload);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
mFlowerAdapter = new FlowerAdapter((FlowerAdapter.FlowerClickListener) this);;
mRecyclerView.setAdapter(mFlowerAdapter);
}
public void getFeed() {
Call<List<Flower>> listCall = mManager.getFlowerService().getAllFlowers();
listCall.enqueue(new Callback<List<Flower>>() {
#Override
public void onResponse(Call<List<Flower>> call, Response<List<Flower>> response) {
if (response.isSuccessful()) {
List<Flower> flowerList = response.body();
for (int i = 0; i < flowerList.size(); i++) {
Flower flower = flowerList.get(i);
SaveIntoDatabase task = new SaveIntoDatabase();
task.execute(flower);
mFlowerAdapter.addFlower(flower);
}
} else {
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
mDialog.dismiss();
}
#Override
public void onFailure(Call<List<Flower>> call, Throwable t) {
mDialog.dismiss();
Toast.makeText(getActivity(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public boolean getNetworkAvailability() {
return Utils.isNetworkAvailable(getActivity());
}
public void onDeliverFlower(Flower flower) {
mFlowerAdapter.addFlower(flower);
}
public void onHideDialog() {
mDialog.dismiss();
}
public class SaveIntoDatabase extends AsyncTask<Flower, Void, Void> {
private final String TAG = SaveIntoDatabase.class.getSimpleName();
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Flower... params) {
Flower flower = params[0];
try {
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
return null;
}
}
}
Lastly the FlowerService class is
public interface FlowerService {
#GET("/routine/first.json")
Call<List<Flower>> getAllFlowers();
}
and FlowerFetchListener Class is
public interface FlowerFetchListener {
void onDeliverAllFlowers(List<Flower> flowers);
void onDeliverFlower(Flower flower);
void onHideDialog();
}
Please Help....and Thanks in advance.
You should not call db and network from activity or fragment. Try to learn MVVM architecture and use ViewModel to store the data from db or network. You may put a lot of effort making your app work but it will still lead to crashes (especially after you introduce fragment and call db and API from there). You will need to handle your data state during configuration changes. Listen to this talk and start writing clean code https://m.youtube.com/watch?v=5qlIPTDE274
The error you get is because you pass wrong parameter into adapter constructor ‘new FlowerAdapter((FlowerAdapter.FlowerClickListener) this)’. If you want to pass a listener to the adapter you need to pass the class which implements the listener: either activity - then pass getActivity(), or fragment ‘this’ - then make fragment implement implements FlowerAdapter.FlowerClickListener.Be aware that it can be null when fragment is not attached to activity, eg configuration change.
I have a database in Firestore and RecycleView to put the data from it. I send some query to Firestore and it get me the result depending on my request. But what, if I want to take 3 random results from the set issued to me on request. That is I get, for example, 20 results and want to take only 3 result each time when I send a request. And each time, it must be random 3 results from this 20?
I was helped to implement the mechanism of selection of 3 random results into the code, but I do not understand how to connect it to my adapter.
public class Myactivity extends AppCompatActivity {
public RecyclerView mResultList;
public FirebaseFirestore mFirestore;
public com.google.firebase.firestore.Query query;
public FirestoreRecyclerAdapter<Places, PlaceViewHolder> firestoreRecyclerAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycle_activity);
mResultList = findViewById(R.id.list_result);
Boolean l_check1 = getIntent().getExtras().getBoolean("1");
Boolean l_check2 = getIntent().getExtras().getBoolean("2");
Boolean l_check3 = getIntent().getExtras().getBoolean("3");
mFirestore = FirebaseFirestore.getInstance();
if (l_check1) {
query = mFirestore.collection("Places").whereEqualTo("colour", "1").limit(20);
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Places> placesList = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
Places place = document.toObject(Places.class);
placesList.add(place);
}
int placeCount = placesList.size();
int randomNumber = new Random().nextInt(placeCount);
List<Places> randomPlaceList = new ArrayList<>();
for (int i=1; i<=3; i++) {
randomPlaceList.add(placesList.get(randomNumber));
}
}
}
});
} else if (l_check2) {
query = mFirestore.collection("Places").whereEqualTo("colour", "2").limit(3);
} else if (l_check3) {
query = mFirestore.collection("Places").whereEqualTo("colour", "3").limit(3);
}
mResultList.setLayoutManager(new LinearLayoutManager(this));
FirestoreRecyclerOptions<Places> options = new FirestoreRecyclerOptions.Builder<Places>()
.setQuery(query, Places.class)
.build();
firestoreRecyclerAdapter = new FirestoreRecyclerAdapter<Places, PlaceViewHolder>(options) {
#Override
protected void onBindViewHolder(PlaceViewHolder holder, int position, Places model) {
holder.setDetails(getApplicationContext(), model.getName(), model.getImage());
}
#Override
public PlaceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_layout, parent, false);
return new PlaceViewHolder(view);
}
};
mResultList.setAdapter(firestoreRecyclerAdapter);
}
#Override
protected void onStart() {
super.onStart();
firestoreRecyclerAdapter.startListening();
}
class PlaceViewHolder extends RecyclerView.ViewHolder {
View mView;
public PlaceViewHolder(View itemView) {
super(itemView);
mView = itemView;
}
public void setDetails(Context context, String placeName, String placeImage) {
final TextView place_Name = mView.findViewById(R.id.text_image_id);
ImageView place_Image = mView.findViewById(R.id.image_id);
place_Name.setText(placeName);
Glide.with(context).load(placeImage).into(place_Image);
place_Name.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), Card_activity.class);
intent.putExtra("qwerty", place_Name.getText());
startActivity(intent);
}
});
}
}
#Override
protected void onStop() {
super.onStop();
if (firestoreRecyclerAdapter != null) {
firestoreRecyclerAdapter.stopListening();
}
}
}
My Place.class:
public class Places {
private String image, name;
public Places() { }
public Places(String image, String name) {
this.image = image;
this.name = name;
}
public String getImage() { return image; }
public String getName() { return name; }
}
First of all, your logic for generating the randomPlaceList is incorrect as you just generate one Random Number using new Random().nextInt(placeCount) and then just keep using it thrice for adding entries to your list.
You should update your logic as follows,
...
final int placeCount = placesList.size();
final Random randomGenerator = new Random();
List<Places> randomPlaceList = new ArrayList<>();
for (int i = 0; i<=3; i++) {
randomPlaceList.add(placesList.get(randomGenerator.nextInt(placeCount)));
}
...
See this thread to see how you can fill the RecyclerView after you fetch the results from Firebase:
How to fill RecyclerView Adapter with Firebase
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.