I'm working with an application, whereas the purpose of it is to keep track of a list of words a person might be interested in learning. The app is split up into 3 activities, in which the first activity (ListActivity) shows a list of words with a few infos (image, word, pronounciation and rating) about each word. Image, word and pronounciation datas are already known and are hardcoded (I know that it would be much better if I just read data from a csv-file but let it be for now), and rating can be set from the EditActivity.
In that context I've implemented code that handles clicks on a specific word that will navigate me to 2nd activity (DetailsActivity) with all relevant infos from the particular word, aswell as retrieving rating data and display at the correct index/item. The code works and everything seems to be fine, but there's a bug, which I really can't solve.
The bug occurs when I do the following:
I click on a particular word in the recyclerview
I edit the word and give it a rating of 8 and press OK. Data will now be sent to ListActivity and will be shown at the correct index/word.
I press on the same word and switch to landscape mode and choose to edit the word. Let's say that I now give it a rating of 5 and press OK.
The new rating data will then be set/shown at index 0 and not the particular word.
What might be the issue here? It seems like it loses position somewhere in the application and between intents, but I can't figure out why.
ListActivity:
public class ListActivity extends AppCompatActivity implements WordAdapter.OnItemListener {
ArrayList<WordItemParcelable> mWords = new ArrayList<WordItemParcelable>();
private WordAdapter mAdapter;
private static final int REQUEST_CODE_DETAILS_ACTIVITY = 1;
private Button exitBtn;
private String rating, note;
private int wordClickedIndex = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
if (savedInstanceState != null) {
mWords = savedInstanceState.getParcelableArrayList(getString(R.string.key_orientationchange));
} else {
mWords = new ArrayList<>();
insertWords();
}
setUpExitBtn();
setUpRecyclerView();
}
public void insertWords() {
mWords.add(new WordItemParcelable(R.drawable.lion, "Lion", "ˈlīən", "A large tawny-coloured cat that lives in prides, found in Africa and NW India. The male has a flowing shaggy mane and takes little part in hunting, which is done cooperatively by the females.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.leopard, "Leopard", "ˈlepərd", "A large solitary cat that has a fawn or brown coat with black spots, native to the forests of Africa and southern Asia.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.cheetah, "Cheetah", "ˈCHēdə", "A large slender spotted cat found in Africa and parts of Asia. It is the fastest animal on land.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.elephant, "Elephant", "ˈeləfənt", "A very large plant-eating mammal with a prehensile trunk, long curved ivory tusks, and large ears, native to Africa and southern Asia. It is the largest living land animal.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.giraffe, "Giraffe", "jəˈraf", "A large African mammal with a very long neck and forelegs, having a coat patterned with brown patches separated by lighter lines. It is the tallest living animal.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.kudo, "Kudu", "ˈko͞odo͞o", "An African antelope that has a greyish or brownish coat with white vertical stripes, and a short bushy tail. The male has long spirally curved horns.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.gnu, "Gnu", "n(y)o͞o", "A large dark antelope with a long head, a beard and mane, and a sloping back.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.oryx, "Oryx", "null", "A large antelope living in arid regions of Africa and Arabia, having dark markings on the face and long horns.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.camel, "Camel", "ˈkaməl", "A large, long-necked ungulate mammal of arid country, with long slender legs, broad cushioned feet, and either one or two humps on the back. Camels can survive for long periods without food or drink, chiefly by using up the fat reserves in their humps.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.shark, "Shark", "SHärk", "A long-bodied chiefly marine fish with a cartilaginous skeleton, a prominent dorsal fin, and tooth-like scales. Most sharks are predatory, though the largest kinds feed on plankton, and some can grow to a large size.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.crocodile, "Crocodile", "ˈkräkəˌdīl", "A large predatory semiaquatic reptile with long jaws, long tail, short legs, and a horny textured skin.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.snake, "Snake", "snāk", "A long limbless reptile which has no eyelids, a short tail, and jaws that are capable of considerable extension. Some snakes have a venomous bite.", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.buffalo, "Buffalo", "ˈbəf(ə)ˌlō", "A heavily built wild ox with backward-curving horns, found mainly in the Old World tropics:", "" + 0.0));
mWords.add(new WordItemParcelable(R.drawable.ostrich, "Ostrich", "ˈästriCH", "A flightless swift-running African bird with a long neck, long legs, and two toes on each foot. It is the largest living bird, with males reaching a height of up to 2.75 m.", "" + 0.0));
}
public void setUpRecyclerView() {
RecyclerView mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
mAdapter = new WordAdapter(mWords, this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
}
#Override
public void onItemClick(int position) {
Intent intent = new Intent(this, DetailsActivity.class);
WordItemParcelable clickedWord = mWords.get(position);
wordClickedIndex = position;
intent.putExtra(getString(R.string.key_picture), clickedWord.getImageResource());
intent.putExtra(getString(R.string.key_name), clickedWord.getWord());
intent.putExtra(getString(R.string.key_pronouncing), clickedWord.getPronouncing());
intent.putExtra(getString(R.string.key_description), clickedWord.getDescription());
intent.putExtra(getString(R.string.key_rating), clickedWord.getRating());
intent.putExtra(getString(R.string.key_notes), clickedWord.getNotes());
startActivityForResult(intent, REQUEST_CODE_DETAILS_ACTIVITY);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_DETAILS_ACTIVITY) {
if (resultCode == RESULT_OK) {
if (data != null) {
rating = data.getStringExtra(getString(R.string.key_rating));
note = data.getStringExtra(getString(R.string.key_notes));
WordItemParcelable i = mWords.get(wordClickedIndex);
i.setRating(rating);
mWords.set(wordClickedIndex, i);
mAdapter.updateData(mWords);
mAdapter.notifyDataSetChanged();
}
}
}
}
public void setUpExitBtn() {
exitBtn = (Button) findViewById(R.id.exitBtn);
exitBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelableArrayList(getString(R.string.key_orientationchange), mWords);
super.onSaveInstanceState(outState);
}
}
Adapter:
public class WordAdapter extends RecyclerView.Adapter<WordAdapter.WordViewHolder> {
private ArrayList<WordItemParcelable> mWordList;
private OnItemListener mOnItemListener;
private int position;
public static class WordViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView mPicture;
TextView txtName, txtPronouncing, txtRating;
OnItemListener onItemListener;
private int position;
public WordViewHolder(#NonNull View itemView, OnItemListener onItemListener) {
super(itemView);
mPicture = itemView.findViewById(R.id.picture);
txtName = itemView.findViewById(R.id.txtNameOfTheWord);
txtPronouncing = itemView.findViewById(R.id.txtPronoucing);
txtRating = itemView.findViewById(R.id.txtListRating);
this.onItemListener = onItemListener;
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
onItemListener.onItemClick(getAdapterPosition());
}
}
public WordAdapter(ArrayList<WordItemParcelable> wordList, OnItemListener onItemListener) {
this.mWordList = wordList;
this.mOnItemListener = onItemListener;
}
#NonNull
#Override
public WordViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.word_item, parent, false);
WordViewHolder wvh = new WordViewHolder(v, mOnItemListener);
return wvh;
}
#Override
public void onBindViewHolder(#NonNull WordViewHolder holder, int position) {
WordItemParcelable word = mWordList.get(position);
int image = word.getImageResource();
String name = word.getWord();
String pronounce = word.getPronouncing();
String rating = word.getRating();
holder.mPicture.setImageResource(image);
holder.txtName.setText(name);
holder.txtPronouncing.setText(pronounce);
holder.txtRating.setText(rating);
}
#Override
public int getItemCount() {
return mWordList.size();
}
public interface OnItemListener {
void onItemClick(int position);
}
public void updateData(ArrayList<WordItemParcelable> newList) {
mWordList = newList;
}
}
When changing the orientation of the screen, activity is recreated to save information, use onsaveinstancestate: https://developer.android.com/guide/components/activities/activity-lifecycle
if (savedInstanceState != null) {
position = savedInstanceState.getInteger(KEY);
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(KEY, position);
super.onSaveInstanceState(outState);
}
(This is pseudo code, there may be errors)
Related
I have encountered a weird bug in my fragment class: imageViews "disappear" if I rotate/reload the view. From my understanding, rotating/reloading destroys the View and re-creates it, so local variables and local view elements may not preserve. However, I have made a manual button that should manually render the images again after I click it, yet the ImageViews stay gone even if I manually reset their imageResource or imageBackground resource. Note that these imageviews are animated-drawables. Below is most of my code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
mView = rootView; //mView is global
return rootView;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d("gnereed id", "id is "+R.id.generate_button);
final Button generate_button = getView().findViewById(R.id.generate_button);
if ( bottleMap == null ) bottleMap = new Hashtable();
// the code sets all images to invisible onCreate
// their visibility will be changed when a bottle is "created"
ImageView[] bottles = new ImageView[7];
for (int i = 0; i < bottleAry.length; i++){
bottles[i] = getView().findViewById(bottleAry[i]);
bottles[i].setVisibility(View.GONE);
}
// this is a button that generates a new bottle and manually refreshes all previous bottle
// If fragment has not been reloaded/rotated then everything works here
// after fragment reloads, new bottles can be generated but old bottles do not re-render.
generate_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Bottle bottle = new Bottle("123", bottleList.size());
bottle.setVisible();
bottleList.add(bottle);
Log.e(" mView : ", mView.toString());
// for all bottles created, re-render them
for (int i = 0; i < bottleList.size(); i ++) {
bottleList.get(i).reRender();
}
}
});
}
public class Bottle{
String message;
ImageView bottleLocation;
int imageSrc;
int avail_index;
int bottle_index;
int locationID;
AnimationDrawable bottleAnimation;
public Bottle(String msg, int bottle_index){
message = msg;
this.bottle_index = bottle_index;
locationID = getRandomBottleLocation();
bottleLocation = getView().findViewById(locationID);
Log.e(" old View : ", getView().toString());
// sets the image source and sets visible, lastly start animation
imageSrc = getRandomBottleImg();
bottleLocation.setBackgroundResource(imageSrc);
bottleMap.put(Integer.toString(locationID), imageSrc);
bottleAnimation = (AnimationDrawable) bottleLocation.getBackground();
bottleAnimation.start();
bottleLocation.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), ViewBottleActivity.class));
availableLocation[avail_index] = false;
bottleMap.remove(Integer.toString(locationID));
bottleAnimation.stop();
bottleLocation.setVisibility(View.GONE);
bottleList.remove(bottle_index);
}
});
}
public int getRandomBottleImg(){
int bottle;
Random rand = new Random();
bottle = imgAry[rand.nextInt(imgAry.length)];
return bottle;
}
public int getRandomBottleLocation(){
int location;
Random rand = new Random();
avail_index = rand.nextInt(bottleAry.length);
while (availableLocation[avail_index]){
avail_index = rand.nextInt(bottleAry.length);
}
location = bottleAry[avail_index];
availableLocation[avail_index] = true;
return location;
}
public void reRender(){
Log.e("location ID is:" , Integer.toString(this.locationID));
bottleLocation = mView.findViewById(this.locationID);
Log.e("ImageView is:" , bottleLocation.toString());
imageSrc = getRandomBottleImg();
bottleLocation.setBackgroundResource(imageSrc);
bottleAnimation = (AnimationDrawable) bottleLocation.getBackground();
bottleAnimation.stop();
bottleAnimation.start();
this.setVisible();
}
public void setVisible(){
bottleLocation.setVisibility(View.VISIBLE);
}
I fixed the problem. What I learned is that you cannot use
View.findViewByID(viewID)
outside of onViewCreated(). Notice how I implemented reRender() function inside the onViewCreated() but it didn't work? The result is that mView.findViewByID(viewID) is actually executed OUTSIDE OF onViewCreated() although I call the function from WITHIN.
Yes, the line will be executed, but upon inspection, mView.findViewByID(viewID) will return TWO DIFFERENT objects when called from inside onViewCreated() and when called from a function that is called from onViewCreated().
This is very counterintuitive, especially for us who's taught to deploy the DRY(do not repeat urself) principle. The fix is just to simply not write outside functions for handling View.
In my android application(Java) I am displaying a list of around 1800 contacts in a recyclerview. While doing a memory profile it was found that when scrolling the recycler view the memory usage was increasing rapidly. So I found this question here which was mentioning the same problem and tried out the solution which was to setIsRecyclable(false) in onBindViewHolder and it worked. The profiling results are given below.
Case 1 : setIsRecyclable(False) not used
Initial memory usage : ~ 40M
[ Java=5.9M Native=5M Graphics=20.3M Stack=0.3M Code=5.3M Others =0.8M ]
Peak memory usage : ~ 345M
[ Java=187.5M Native=39.1M Graphics=101.5M Stack=0.4M Code=11.6M Others =6.5M ]
Also the peak memory usage was found to increase with increase in number of items in the list. After the continuous scrolling is stopped for a while the memory usage does come down but only to around 162 MB.
Case 2 : after adding setIsRecyclable(False) to onBindViewHolder
Initial memory usage : ~ 42M
[ Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others =0.8M ]
Peak memory usage : ~ 100M
[ Java=43.9M Native=9.7M Graphics=32.6M Stack=0.4M Code=11.7M Others =2.2M ]
Also, in this case, memory usage was not affected significantly by increasing number of items in list. Although peak memory usage is about 100MB the average stays at around 70 MB for most of the time which is even better.
Source Code of fragment containing recyclerView
Note :
* Adapter class is defined as an inner class of Fragment class and ViewHolder class is defined as an inner class of Adapter class.
* 'App.personList' is a static arrayList holding the list of contacts and App is the ViewModel class.
* adapter1 is the only adapter of interest. Please avoid adapter2(handles another small list)
public class FragmentAllContacts extends Fragment
{
public static MainActivity main;
public RecyclerView contactsView, tagsView;
LinearLayoutManager llm, lln;
Button filterCloseButton;
CardView filterView;
Adapter_ContactListView adapter1;
Adapter_TagListView adapter2;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
setHasOptionsMenu(true);
main = (MainActivity) getActivity();
return inflater.inflate(R.layout.fragment_all_contacts, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
adapter1 = new Adapter_ContactListView(App.personList,getContext());
adapter2 = new Adapter_TagListView(App.tagList,getContext());
filterView = getView().findViewById(R.id.cardView7);
FloatingActionButton fab = getView().findViewById(R.id.create_contact_fab);
contactsView = getView().findViewById(R.id.allContacts_recyclerView);
contactsView.setAdapter(adapter1);
llm = new LinearLayoutManager(main.getBaseContext());
contactsView.setLayoutManager(llm);
contactsView.scrollToPosition(App.AllConnections.scrollPosition);
tagsView = getView().findViewById(R.id.allTags_recyclerView);
tagsView.setAdapter(adapter2);
lln = new LinearLayoutManager(main.getBaseContext(), LinearLayoutManager.HORIZONTAL, false);
tagsView.setLayoutManager(lln);
}
class Adapter_ContactListView extends RecyclerView.Adapter<Adapter_ContactListView.ViewHolder> implements Filterable {
List<Person_PersistentData> contactsFiltered;
Context context;
public Adapter_ContactListView(List<Person_PersistentData> list, Context context)
{
this.contactsFiltered = list;
this.context = context;
}
#Override
public Adapter_ContactListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_list, parent, false);
Adapter_ContactListView.ViewHolder pane = new Adapter_ContactListView.ViewHolder(v);
return pane;
}
#Override
public void onBindViewHolder(Adapter_ContactListView.ViewHolder pane, int position)
{
pane.setIsRecyclable(false);
final Person_PersistentData rec = contactsFiltered.get(position);
pane.nameView.setText(rec.personName + " (" + rec.personID + ")");
Uri imageUri = App.FSManager.getProfilePic(rec.personID);
if (imageUri != null) {pane.imageView.setImageURI(imageUri);}
else {pane.imageView.setImageResource(R.drawable.ico_60px);}
if (App.AllConnections.personSelectionStack.contains(rec.personID)) {pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_000_070_100));}
else
{pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_020_020_020));}
pane.cv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
if(App.AllConnections.selectionMode)
{
App.Person_SelectionInput(rec.personID);
Adapter_ContactListView.this.notifyDataSetChanged();
}
else
{
App.PersonInfo.id = rec.personID;
main.startTask(T.personInfo);
}
}
});
pane.cv.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view)
{
App.Person_SelectionInput(rec.personID);
Adapter_ContactListView.this.notifyDataSetChanged();
return false;
}
});
//animate(holder);
}
#Override
public int getItemCount() {
//returns the number of elements the RecyclerView will display
return contactsFiltered.size();
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public Filter getFilter() { ... }
#Override
public long getItemId(int position)
{
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
class ViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView nameView;
ImageView imageView;
public ViewHolder(#NonNull View itemView)
{
super(itemView);
cv = itemView.findViewById(R.id.cardView);
nameView = itemView.findViewById(R.id.name);
imageView = itemView.findViewById(R.id.imageViewZ);
}
}
}
}
Question
So 'setIsRecyclable(False)' is supposed to prevent recycling of views and this should be causing more memory usage. But instead it is showing the opposite behavior. Also i think the app will surely crash if it has to handle an even larger list without using setIsRecyclable(false). Why is this happening ?
getItemId(int) and getItemViewType(int) should NEVER return position itself, you're violating recyclerview contract by forcing it to create new viewholders for every single position intead of re-using existing views.
This is the cause of your issue - every position has unique itemViewType so they start to fill up recycledViewPool very rapidly since they're only being inserted and never being taken out of it. setIsRecyclable(False) circumvents the issue by not putting them in recyclerViewPool but that doesn't fix the problem of lack of view recycling.
Just delete getItemId and getItemViewType overrides because you're not using them properly.
I'm trying to send intent from fragment to MainActivity. But while doing startActivity(intent) at this point app is working good but every time I click on listview fragment is refreshing and listview starts from 0 index again. What is the best way to send the intent from the fragment?
I have two fragments and one main activity. So the user can see both the fragments at the same time. fist fragment is a list of cities and the second fragment is the description of the city.
Thank you very much for your time and assistance in this matter.
Please check my code below:
public class MainActivity extends AppCompatActivity {
private String cityName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManage();
}
public void fragmentManage() {
CityInformationFragment cityInformationFragment = new
CityInformationFragment();
CityList cityList = new CityList();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment2holder, cityList);
fragmentTransaction.add(R.id.fragment1holder, cityInformationFragment);
fragmentTransaction.commit();
Intent intent = getIntent();
cityName = intent.getStringExtra(CityList.PUT_EXTRA_KEY);
Bundle bundle = new Bundle();
bundle.putString(CityList.PUT_EXTRA_KEY, cityName);
cityInformationFragment.setArguments(bundle);
}
}
My CityList fragment:
public class CityList extends Fragment implements
AdapterView.OnItemClickListener {
String[] cityList;
ListView listView;
protected static final String PUT_EXTRA_KEY = "city";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup
container, Bundle savedInstanceState) {
// arrayAdapter.addAll(cityList);
cityList = getResources().getStringArray(R.array.cities);
ArrayAdapter<CharSequence> arrayAdapter = new ArrayAdapter <CharSequence>(getActivity(), android.R.layout.simple_list_item_1, cityList);
View view = inflater.inflate(R.layout.citylistframent, container, false);
listView = (ListView) view.findViewById(R.id.cityList);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(this);
return view;
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView) view;
String city = textView.getText().toString();
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.putExtra(PUT_EXTRA_KEY, city);
startActivity(intent);
}
}
My CityInformationFragment:
public class CityInformationFragment extends Fragment {
String vancouver = "Vancouver, a bustling west coast seaport in British Columbia, is among Canada’s densest, most ethnically diverse cities." +
" A popular filming location, it’s surrounded by mountains, and also has thriving art, theatre and music scenes." +
" Vancouver Art Gallery is known for its works by regional artists, while the Museum of Anthropology houses preeminent First Nations collections.";
String calgary = "Calgary, a cosmopolitan Alberta city with numerous skyscrapers, owes its rapid growth to its status as the centre of Canada’s oil industry. However," +
" it’s still steeped in the western culture that earned it the nickname “Cowtown,”" +
" evident in the Calgary Stampede, its massive July rodeo and festival that grew out of the farming exhibitions once presented here.";
String kamloops = "Kamloops is a Canadian city in British Columbia, where the North and South Thompson rivers meet." +
" Sun Peaks Resort’s hiking trails, bike park and numerous ski runs lie to the northeast. Cougars and bears inhabit the British Columbia Wildlife Park east of town." +
" West, above Kamloops Lake are clay hoodoos (or spires). The riverside Secwepemc Museum & Heritage Park features the remains of a 2,000-year-old village.";
String toronto = "Toronto, the capital of the province of Ontario, is a major Canadian city along Lake Ontario’s northwestern shore." +
" It's a dynamic metropolis with a core of soaring skyscrapers, all dwarfed by the iconic, free-standing CN Tower. " +
"Toronto also has many green spaces, from the orderly oval of Queen’s Park to 400-acre High Park and its trails, sports facilities and zoo.";
String saskatoon = "Saskatoon is a city straddling the South Saskatchewan River in Saskatchewan, Canada. " +
"North along the riverside Meewasin Trail is Wanuskewin Heritage Park, with exhibitions exploring indigenous culture. " +
"On the trail’s southern stretch, native wildlife inhabit the prairie grasslands of Beaver Creek Conservation Area. " +
"East of the river, the Saskatoon Forestry Farm Park & Zoo has manicured gardens and a children’s zoo.";
TextView textView;
String cityName = "";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.cityinformation, container, false);
textView = view.findViewById(R.id.cityInformation);
cityName = getArguments().getString(CityList.PUT_EXTRA_KEY);
// Toast.makeText(getContext(), cityName, Toast.LENGTH_SHORT).show();
setInformation();
return view;
}
public String getVancouver() {
return vancouver;
}
public String getCalgary() {
return calgary;
}
public String getKamloops() {
return kamloops;
}
public String getToronto() {
return toronto;
}
public String getSaskatoon() {
return saskatoon;
}
public void setInformation() {
if (cityName != null) {
if (cityName.equals("")) {
textView.setText(getKamloops());
} else if (cityName.equals("Calgary")) {
textView.setText(getCalgary());
} else if (cityName.equals("Kamloops")) {
textView.setText(getKamloops());
} else if(cityName.equals("Calgary")) {
textView.setText(getCalgary());
} else if(cityName.equals("Saskatoon")){
textView.setText(getSaskatoon());
} else if(cityName.equals("Toronto")) {
textView.setText(getToronto());
} else if(cityName.equals("Vancouver")){
textView.setText(getVancouver());
}
} else {
textView.setText(getKamloops());
}
}
}
Use eventbus it will solve your problem and you dont need to call activity, fragments are working inside activity read documentation you understand me
Intents are only usable for sending data on an Activity level. To pass data between fragments we need to create our own interfaces.
As describe here Communicating with Other Fragments
You need to create a listener/callback in your Activity and Fragment. Whenever there is a change that you want to pass to Activity, send it via the listener. Read more at Communicating with Other Fragments.
First, define the listener with interface in the Fragment:
public class CityList extends Fragment implements AdapterView.OnItemClickListener {
private CityListListener mListener;
// Host Activity must implement this listener.
public interface CityListListener {
public void onItemClicked(String city);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Makes sure that the host activity has implemented the listener
try {
mListener = (CityListListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement CityListListener");
}
}
...
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView) view;
String city = textView.getText().toString();
// tell the host Activity about the item click.
mListener.onItemClicked(city);
}
}
Now, you need to implement the listener in your Activity:
public class MainActivity extends AppCompatActivity
implements CityList.CityListListener {
...
#Override
public void onItemClicked(String city) {
// This will listen to every item click in CityList Fragment
// Do something about the city.
}
}
I practicing a app. where i made a Place Array, A grid view which displays 0th position in GridView and 1st position in ImageView for grid view, 2nd position is displayed when clicked on the gridview in another activity called answer. in answer activity when swiped i want it to go to another question but am not getting it. Can u help me Out.here is the code bellow.
public class QuestionsActivity extends Activity {
public Place[] myPlaceArray = new Place[]{
new Place(1, "settings" , "1. Given the choice of"),
new Place(2, "settings" , "2. Would you like to "),
new Place(3, "settings" , "3. Before making a telephone call"),
new Place(4, "settings" , "4. What would constitute "),
new Place(5, "settings" , "5. When did you last"),
new Place(6, "settings" , "6. If you were "),
new Place(7, "settings" , "7. Do you have a secret "),
new Place(8, "settings" , "8. Name three things you "),
new Place(9, "settings" , "9. For what in your "),
new Place(10, "settings" , "10. If you could ")
};
private GridView numgridView;
private ArrayAdapter mPlaceAdapter;
public int clickedPosition;
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.question_activity);
numgridView = (GridView) findViewById(R.id.numGrid);
mPlaceAdapter = new PlaceAdapter(getApplicationContext(),R.layout.question_numbers_view,myPlaceArray);
if (numgridView!=null){
numgridView.setAdapter(mPlaceAdapter);
}
numgridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
clickedPosition = position;
Log.v("PLACE", myPlaceArray[position].Question);
Intent intent = new Intent(QuestionsActivity.this, answer.class);
intent.putExtra("theAnswer",myPlaceArray[position].Question );
intent.putExtra("theNextAnswer",myPlaceArray[clickedPosition ++].Question ); // this is what i tried but it dint work
intent.putExtra("thePreviousAnswer",myPlaceArray[clickedPosition --].Question ); // this is what i tried but it dint work
startActivity(intent);
Toast.makeText(QuestionsActivity.this, "Touch And Hold To Copy The Text",
Toast.LENGTH_LONG).show();
}
});
}
}
i passed it to another activity with key and when swiped i want it to go to the next question.
public class answer extends Activity implements AdColonyAdAvailabilityListener, AdColonyAdListener {
TextView textView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.answer);
AdColony.configure(this, "version:1.0,store:google", APP_ID, ZONE_ID);
AdColony.addAdAvailabilityListener(this);
textView = (TextView) findViewById(R.id.answer);
textView.setText(getIntent().getExtras().getString("theAnswer"));
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
}
});
textView.setOnTouchListener(new OnSwipeTouchListener() {
public boolean onSwipeRight() {
Toast.makeText(answer.this, "right", Toast.LENGTH_SHORT).show();
textView.setText(getIntent().getExtras().getString("thePreviousAnswer"));
return true;
// here when i swiped i want it to go to next question
}
public boolean onSwipeLeft() {
Toast.makeText(answer.this, "left", Toast.LENGTH_SHORT).show();
textView.setText(getIntent().getExtras().getString("theNextAnswer"));
return true;
//here i want it go to previous question
}
});
if(AdColony.statusForZone(ZONE_ID).equals("active"))
{
textView.setEnabled( true );
}
}
I have a RecyclerView with an TextView text box and a cross button ImageView. I have a button outside of the recyclerview that makes the cross button ImageView visible / gone.
I'm looking to remove an item from the recylerview, when that items cross button ImageView is pressed.
My adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener, View.OnLongClickListener {
private ArrayList<String> mDataset;
private static Context sContext;
public MyAdapter(Context context, ArrayList<String> myDataset) {
mDataset = myDataset;
sContext = context;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);
ViewHolder holder = new ViewHolder(v);
holder.mNameTextView.setOnClickListener(MyAdapter.this);
holder.mNameTextView.setOnLongClickListener(MyAdapter.this);
holder.mNameTextView.setTag(holder);
return holder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mNameTextView.setText(mDataset.get(position));
}
#Override
public int getItemCount() {
return mDataset.size();
}
#Override
public void onClick(View view) {
ViewHolder holder = (ViewHolder) view.getTag();
if (view.getId() == holder.mNameTextView.getId()) {
Toast.makeText(sContext, holder.mNameTextView.getText(), Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onLongClick(View view) {
ViewHolder holder = (ViewHolder) view.getTag();
if (view.getId() == holder.mNameTextView.getId()) {
mDataset.remove(holder.getPosition());
notifyDataSetChanged();
Toast.makeText(sContext, "Item " + holder.mNameTextView.getText() + " has been removed from list",
Toast.LENGTH_SHORT).show();
}
return false;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mNumberRowTextView;
public TextView mNameTextView;
public ViewHolder(View v) {
super(v);
mNameTextView = (TextView) v.findViewById(R.id.nameTextView);
}
}
}
My layout is:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:id="#+id/layout">
<TextView
android:id="#+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
android:background="#drawable/greyline"/>
<ImageView
android:id="#+id/crossButton"
android:layout_width="16dp"
android:layout_height="16dp"
android:visibility="gone"
android:layout_marginLeft="50dp"
android:src="#drawable/cross" />
</LinearLayout>
How can I get something like an onClick working for my crossButton ImageView? Is there a better way? Maybe changing the whole item onclick into a remove the item? The recyclerview shows a list of locations that need to be edited. Any technical advice or comments / suggestions on best implementation would be hugely appreciated.
I have done something similar.
In your MyAdapter:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public CardView mCardView;
public TextView mTextViewTitle;
public TextView mTextViewContent;
public ImageView mImageViewContentPic;
public ImageView imgViewRemoveIcon;
public ViewHolder(View v) {
super(v);
mCardView = (CardView) v.findViewById(R.id.card_view);
mTextViewTitle = (TextView) v.findViewById(R.id.item_title);
mTextViewContent = (TextView) v.findViewById(R.id.item_content);
mImageViewContentPic = (ImageView) v.findViewById(R.id.item_content_pic);
//......
imgViewRemoveIcon = (ImageView) v.findViewById(R.id.remove_icon);
mTextViewContent.setOnClickListener(this);
imgViewRemoveIcon.setOnClickListener(this);
v.setOnClickListener(this);
mTextViewContent.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(view, getPosition());
}
return false;
}
});
}
#Override
public void onClick(View v) {
//Log.d("View: ", v.toString());
//Toast.makeText(v.getContext(), mTextViewTitle.getText() + " position = " + getPosition(), Toast.LENGTH_SHORT).show();
if(v.equals(imgViewRemoveIcon)){
removeAt(getPosition());
}else if (mItemClickListener != null) {
mItemClickListener.onItemClick(v, getPosition());
}
}
}
public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
public void removeAt(int position) {
mDataset.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, mDataSet.size());
}
Edit:
getPosition() is deprecated now, use getAdapterPosition() instead.
first of all, item should be removed from the list!
mDataSet.remove(getAdapterPosition());
then:
notifyItemRemoved(getAdapterPosition());
notifyItemRangeChanged(getAdapterPosition(), mDataSet.size()-getAdapterPosition());
if still item not removed use this magic method :)
private void deleteItem(int position) {
mDataSet.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, mDataSet.size());
holder.itemView.setVisibility(View.GONE);
}
Kotlin version
private fun deleteItem(position: Int) {
mDataSet.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mDataSet.size)
holder.itemView.visibility = View.GONE
}
The Problem
RecyclerView was built to display data in an efficient and responsive manner.
Usually you have a dataset which is passed to your adapter and is looped through to display your data.
Here your dataset is:
private ArrayList<String> mDataset;
The point is that RecyclerView is not connected to your dataset, and therefore is unaware of your dataset changes.
It just reads data once and displays it through your ViewHolder, but a change to your dataset will not propagate to your UI.
This means that whenever you make a deletion/addition on your data list, those changes won't be reflected to your RecyclerView directly. (i.e. you remove the item at index 5, but the 6th element remains in your recycler view).
A (old school) solution
RecyclerView exposes some methods for you to communicate your dataset changes, reflecting those changes directly on your list items.
The standard Android APIs allow you to bind the process of data removal (for the purpose of the question) with the process of View removal.
The methods we are talking about are:
notifyItemChanged(index: Int)
notifyItemInserted(index: Int)
notifyItemRemoved(index: Int)
notifyItemRangeChanged(startPosition: Int, itemCount: Int)
notifyItemRangeInserted(startPosition: Int, itemCount: Int)
notifyItemRangeRemoved(startPosition: Int, itemCount: Int)
A Complete (old school) Solution
If you don't properly specify what happens on each addition, change or removal of items, RecyclerView list items are animated unresponsively because of a lack of information about how to move the different views around the list.
The following code will allow RecyclerView to precisely play the animation with regards to the view that is being removed (And as a side note, it fixes any IndexOutOfBoundExceptions, marked by the stacktrace as "data inconsistency").
void remove(position: Int) {
dataset.removeAt(position)
notifyItemChanged(position)
notifyItemRangeRemoved(position, 1)
}
Under the hood, if we look into RecyclerView we can find documentation explaining that the second parameter we pass to notifyItemRangeRemoved is the number of items that are removed from the dataset, not the total number of items (As wrongly reported in some others information sources).
/**
* Notify any registered observers that the <code>itemCount</code> items previously
* located at <code>positionStart</code> have been removed from the data set. The items
* previously located at and after <code>positionStart + itemCount</code> may now be found
* at <code>oldPosition - itemCount</code>.
*
* <p>This is a structural change event. Representations of other existing items in the data
* set are still considered up to date and will not be rebound, though their positions
* may be altered.</p>
*
* #param positionStart Previous position of the first item that was removed
* #param itemCount Number of items removed from the data set
*/
public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
mObservable.notifyItemRangeRemoved(positionStart, itemCount);
}
Open source solutions
You can let a library like FastAdapter, Epoxy or Groupie take care of the business, and even use an observable recycler view with data binding.
New ListAdapter
Google recently introduced a new way of writing the recycler view adapter, which works really well and supports reactive data.
It is a new approach and requires a bit of refactoring, but it is 100% worth switching to it, as it makes everything smoother.
here is the documentation, and here a medium article explaining it
Here are some visual supplemental examples. See my fuller answer for examples of adding and removing a range.
Add single item
Add "Pig" at index 2.
String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);
Remove single item
Remove "Pig" from the list.
int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);
Possibly a duplicate answer but quite useful for me. You can implement the method given below in RecyclerView.Adapter<RecyclerView.ViewHolder>
and can use this method as per your requirements, I hope it will work for you
public void removeItem(#NonNull Object object) {
mDataSetList.remove(object);
notifyDataSetChanged();
}
I tried all the above answers, but inserting or removing items to recyclerview causes problem with the position in the dataSet. Ended up using delete(getAdapterPosition()); inside the viewHolder which worked great at finding the position of items.
The problem I had was I was removing an item from the list that was no longer associated with the adapter to make sure you are modifying the correct adapter you can implement a method like this in your adapter:
public void removeItemAtPosition(int position) {
items.remove(position);
}
And call it in your fragment or activity like this:
adapter.removeItemAtPosition(position);
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List<cardview_widgets> list;
public MyAdapter(Context context, List<cardview_widgets> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(this.context).inflate(R.layout.fragment1_one_item,
viewGroup, false);
return new MyViewHolder(view);
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtValue;
TextView txtCategory;
ImageView imgInorEx;
ImageView imgCategory;
TextView txtDate;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
txtValue= itemView.findViewById(R.id.id_values);
txtCategory= itemView.findViewById(R.id.id_category);
imgInorEx= itemView.findViewById(R.id.id_inorex);
imgCategory= itemView.findViewById(R.id.id_imgcategory);
txtDate= itemView.findViewById(R.id.id_date);
}
}
#NonNull
#Override
public void onBindViewHolder(#NonNull final MyViewHolder myViewHolder, int i) {
myViewHolder.txtValue.setText(String.valueOf(list.get(i).getValuee()));
myViewHolder.txtCategory.setText(list.get(i).getCategory());
myViewHolder.imgInorEx.setBackgroundColor(list.get(i).getImg_inorex());
myViewHolder.imgCategory.setImageResource(list.get(i).getImg_category());
myViewHolder.txtDate.setText(list.get(i).getDate());
myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
list.remove(myViewHolder.getAdapterPosition());
notifyDataSetChanged();
return false;
}
});
}
#Override
public int getItemCount() {
return list.size();
}}
i hope this help you.
if you want to remove item you should do this:
first remove item:
phones.remove(position);
in next step you should notify your recycler adapter that you remove an item by this code:
notifyItemRemoved(position);
notifyItemRangeChanged(position, phones.size());
but if you change an item do this:
first change a parameter of your object like this:
Service s = services.get(position);
s.done = "Cancel service";
services.set(position,s);
or new it like this :
Service s = new Service();
services.set(position,s);
then notify your recycler adapter that you modify an item by this code:
notifyItemChanged(position);
notifyItemRangeChanged(position, services.size());
hope helps you.
String str = arrayList.get(position);
arrayList.remove(str);
MyAdapter.this.notifyDataSetChanged();
To Method onBindViewHolder Write This Code
holder.remove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Cursor del=dbAdapter.ExecuteQ("delete from TblItem where Id="+values.get(position).getId());
values.remove(position);
notifyDataSetChanged();
}
});
Incase Anyone wants to implement something like this in Main class instead of Adapter class, you can use:
public void removeAt(int position) {
peopleListUser.remove(position);
friendsListRecycler.getAdapter().notifyItemRemoved(position);
friendsListRecycler.getAdapter().notifyItemRangeChanged(position, peopleListUser.size());
}
where friendsListRecycler is the Adapter name
you must to remove this item from arrayList of data
myDataset.remove(holder.getAdapterPosition());
notifyItemRemoved(holder.getAdapterPosition());
notifyItemRangeChanged(holder.getAdapterPosition(), getItemCount());
//////// set the position
holder.cancel.setTag(position);
///// click to remove an item from recycler view and an array list
holder.cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int positionToRemove = (int)view.getTag(); //get the position of the view to delete stored in the tag
mDataset.remove(positionToRemove);
notifyDataSetChanged();
}
});
make interface into custom adapter class and handling click event on recycler view..
onItemClickListner onItemClickListner;
public void setOnItemClickListner(CommentsAdapter.onItemClickListner onItemClickListner) {
this.onItemClickListner = onItemClickListner;
}
public interface onItemClickListner {
void onClick(Contact contact);//pass your object types.
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
// below code handle click event on recycler view item.
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onItemClickListner.onClick(mContectList.get(position));
}
});
}
after define adapter and bind into recycler view called below code..
adapter.setOnItemClickListner(new CommentsAdapter.onItemClickListner() {
#Override
public void onClick(Contact contact) {
contectList.remove(contectList.get(contectList.indexOf(contact)));
adapter.notifyDataSetChanged();
}
});
}
In case you are wondering like I did where can we get the adapter position in the method getadapterposition(); its in viewholder object.so you have to put your code like this
mdataset.remove(holder.getadapterposition());
In the activity:
mAdapter.updateAt(pos, text, completed);
mAdapter.removeAt(pos);
In the your adapter:
void removeAt(int position) {
list.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, list.size());
}
void updateAt(int position, String text, Boolean completed) {
TodoEntity todoEntity = list.get(position);
todoEntity.setText(text);
todoEntity.setCompleted(completed);
notifyItemChanged(position);
}
in 2022, after trying everything the whole internet given below is the answer
In MyViewHolder class
private myAdapter adapter;
inside MyViewHolder function initalise adapter
adapter = myAdapter.this
inside onclick
int position = getAdapterPosition()
list.remove(position);
adapter.notifyItemRemoved(position);