I am trying to use a swipeCallback on a list with modeladapter. In order to make it work, I stripped down all my customization and modeled it close to the sample app, but the combination produces the error of not allowing undo. When I swipe, this happens:
The swipe works, but the undo icon does not show up. Any ideas what I am doing wrong? The underlying fragment is this:
public class EditFragment extends Fragment implements ItemTouchCallback, SimpleSwipeCallback.ItemSwipeCallback {
private FragmentEditBinding oBinding;
private SongViewModel oViewModel;
//save our FastAdapter
private FastAdapter fastAdapter;
private ModelAdapter<ModelSongCounter, ModelItemView> itemAdapter;
//drag & drop
private SimpleDragCallback touchCallback;
private ItemTouchHelper touchHelper;
public EditFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//init Databinding
oBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_edit, container, false);//.setContentView(getActivity(), R.layout.fragment_main);
//LayoutInflaterCompat.setFactory(getLayoutInflater(), new IconicsLayoutInflater(getActivity()));
//style our ui
new MaterializeBuilder().withActivity(getActivity()).build();
//adapters
//FastScrollIndicatorAdapter fastScrollIndicatorAdapter = new FastScrollIndicatorAdapter();
itemAdapter = new ModelAdapter<>(new IInterceptor<ModelSongCounter, ModelItemView>() {
#Override
public ModelItemView intercept(ModelSongCounter iconModel) {
return new ModelItemView(iconModel);
}
});
//create our FastAdapter which will manage everything
fastAdapter = FastAdapter.with(Arrays.asList(itemAdapter));
fastAdapter.withSelectable(true);
//get our recyclerView and do basic setup
//RecyclerView rv = oBinding.SongRecyclerView;
oBinding.SongRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
//oBinding.SongRecyclerView.setItemAnimator(new SlideDownAlphaAnimator());
oBinding.SongRecyclerView.setAdapter(fastAdapter);
//get ViewModels from Provider
oViewModel = ViewModelProviders.of(getActivity()).get(SongViewModel.class);
//get rid of the annoying blink
oBinding.SongRecyclerView.setItemAnimator(null);
//add Observer to ViewModel
// The onChanged() method fires when the observed data changes and the activity is
// in the foreground.
oViewModel.getAllCatsLive().observe(this, new Observer<List<ModelSongCounter>>() {
#Override
public void onChanged(#Nullable List<ModelSongCounter> modelSongCounters) {
itemAdapter.set(modelSongCounters);
}
});
fastAdapter.withEventHook(new ClickEventHook<ModelItemView>() {
#Nullable
#Override
public View onBind(#NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof ModelItemView.ViewHolder) {
return ((ModelItemView.ViewHolder) viewHolder).Minus;
}
return null;
}
#Override
public void onClick(View v, int position, FastAdapter<ModelItemView> fastAdapter, ModelItemView item) {
//react on the click event
oViewModel.decrement(item.getModel().uid);
}
});
fastAdapter.withEventHook(new ClickEventHook<ModelItemView>() {
#Nullable
#Override
public View onBind(#NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof ModelItemView.ViewHolder) {
return ((ModelItemView.ViewHolder) viewHolder).Plus;
}
return null;
}
#Override
public void onClick(View v, int position, FastAdapter<ModelItemView> fastAdapter, ModelItemView item) {
//react on the click event
oViewModel.increment(item.getModel().uid);
}
});
//restore selections (this has to be done after the items were added
fastAdapter.withSavedInstanceState(savedInstanceState);
//Swipable stuff within OnCreateView
Drawable leaveBehindDrawableLeft = new IconicsDrawable(getContext())
.icon(MaterialDesignIconic.Icon.gmi_delete)
.color(Color.WHITE)
.sizeDp(24);
Drawable leaveBehindDrawableRight = new IconicsDrawable(getContext())
.icon(MaterialDesignIconic.Icon.gmi_archive)
.color(Color.WHITE)
.sizeDp(24);
touchCallback = new SimpleSwipeDragCallback(
this,
this,
leaveBehindDrawableLeft,
ItemTouchHelper.LEFT,
ContextCompat.getColor(getContext(), R.color.md_red_900)
)
.withBackgroundSwipeRight(ContextCompat.getColor(getContext(), R.color.md_blue_900))
.withLeaveBehindSwipeRight(leaveBehindDrawableRight);
touchHelper = new ItemTouchHelper(touchCallback); // Create ItemTouchHelper and pass with parameter the SimpleDragCallback
touchHelper.attachToRecyclerView(oBinding.SongRecyclerView); // Attach ItemTouchHelper to RecyclerView
//restore selections (this has to be done after the items were added
fastAdapter.withSavedInstanceState(savedInstanceState);
return oBinding.getRoot();
}
#Override
public void onSaveInstanceState(Bundle outState) {
//add the values which need to be saved from the adapter to the bundle
outState = fastAdapter.saveInstanceState(outState);
super.onSaveInstanceState(outState);
}
//Swipable...and probably relevant for expandables, since there is TouchOnMove
#Override
public boolean itemTouchOnMove(int oldPosition, int newPosition) {
//DragDropUtil.onMove((ItemAdapter)itemAdapter, oldPosition, newPosition); // change position
return true;
}
#Override
public void itemTouchDropped(int oldPosition, int newPosition) {
//f.e. save new order in database
}
#Override
public void itemSwiped(int position, int direction) {
// -- Option 1: Direct action --
//do something when swiped such as: select, remove, update, ...:
//A) fastItemAdapter.select(position);
//B) fastItemAdapter.remove(position);
//C) update item, set "read" if an email etc
// -- Option 2: Delayed action --
final ModelItemView item = itemAdapter.getAdapterItem(position);
item.setSwipedDirection(direction);
// This can vary depending on direction but remove & archive simulated here both results in
// removal from list
final Runnable removeRunnable = new Runnable() {
#Override
public void run() {
item.setSwipedAction(null);
int position = itemAdapter.getAdapterPosition(item);
if (position != RecyclerView.NO_POSITION) {
//this sample uses a filter. If a filter is used we should use the methods provided by the filter (to make sure filter and normal state is updated)
//fastItemAdapter.getItemFilter().remove(position);
itemAdapter.remove(position);
}
}
};
final View rv = oBinding.SongRecyclerView;
rv.postDelayed(removeRunnable, 3000);
item.setSwipedAction(new Runnable() {
#Override
public void run() {
rv.removeCallbacks(removeRunnable);
item.setSwipedDirection(0);
int position = itemAdapter.getAdapterPosition(item);
if (position != RecyclerView.NO_POSITION) {
fastAdapter.notifyItemChanged(position);
}
}
});
fastAdapter.notifyItemChanged(position);
//TODO can this above be made more generic, along with the support in the item?
}
}
This is the swipable ModelItem (the model "ModelSongCounter" is just a POJO):
public class ModelItemView
extends ModelAbstractItem<ModelSongCounter, ModelItemView, ModelItemView.ViewHolder>
implements ISwipeable<ModelItemView, IItem>, IDraggable<ModelItemView, IItem> {
public StringHolder undoTextSwipeFromLeft;
public int iSwipedDirection;
private Runnable rSwipedAction;
public boolean bSwipable = true;
public boolean draggable = true;
public ModelItemView(ModelSongCounter icon) {
super(icon);
}
/**
* defines the type defining this item. must be unique. preferably an id
*
* #return the type
*/
#Override
public int getType() {
return R.id.iconics_tag_id;
}
/**
* defines the layout which will be used for this item in the list
*
* #return the layout for this item
*/
#Override
public int getLayoutRes() {
return R.layout.item_view;
}
/**
* binds the data of this item onto the viewHolder
*
* #param viewHolder the viewHolder of this item
*/
#Override
public void bindView(ViewHolder viewHolder, List<Object> payloads) {
super.bindView(viewHolder, payloads);
//define our data for the view
viewHolder.name.setText(getModel().getName());
viewHolder.counter.setText(Integer.toString(getModel().getCounter()));
viewHolder.swipeResultContent.setVisibility(iSwipedDirection != 0 ? View.VISIBLE : View.GONE);
viewHolder.itemContent.setVisibility(iSwipedDirection != 0 ? View.GONE : View.VISIBLE);
CharSequence swipedAction = null;
CharSequence swipedText = null;
if(iSwipedDirection != 0){
swipedAction = viewHolder.itemView.getContext().getString(R.string.action_undo);
swipedText = iSwipedDirection == ItemTouchHelper.LEFT ? "Removed" : "Archived - Should not be implemented!";
viewHolder.swipeResultContent.setBackgroundColor(
ContextCompat.getColor(viewHolder.itemView.getContext(),
iSwipedDirection == ItemTouchHelper.LEFT ? R.color.md_red_900 : R.color.md_blue_900));
}
viewHolder.swipedAction.setText(swipedAction == null ? "" : swipedAction);
viewHolder.swipedText.setText(swipedText == null ? "" : swipedText);
viewHolder.rSwipedActionRunnable = this.rSwipedAction;
}
#Override
public void unbindView(ViewHolder holder) {
super.unbindView(holder);
holder.name.setText(null);
holder.counter.setText(null);
holder.swipedAction.setText(null);
holder.swipedText.setText(null);
holder.rSwipedActionRunnable = this.rSwipedAction;
}
#Override
public ViewHolder getViewHolder(View v) {
return new ViewHolder(v);
}
//SWipable
#Override
public boolean isSwipeable() {
return this.bSwipable;
}
#Override
public ModelItemView withIsSwipeable(boolean swipeableP) {
this.bSwipable = swipeableP;
return this;
}
public void setSwipedDirection(int iSwipedDirectionP){
this.iSwipedDirection = iSwipedDirectionP;
}
public void setSwipedAction(Runnable actionP){
this.rSwipedAction = actionP;
}
#Override
public boolean isDraggable() {
return draggable;
}
#Override
public ModelItemView withIsDraggable(boolean draggableP) {
this.draggable = draggableP;
return this;
}
/**
* our ViewHolder
*/
protected static class ViewHolder extends RecyclerView.ViewHolder {
protected View view;
#BindView(R.id.material_drawer_song)
public TextView name;
#BindView(R.id.material_drawer_counter)
public TextView counter;
#BindView(R.id.material_drawer_minus)
public ImageView Minus;
#BindView(R.id.material_drawer_plus)
public ImageView Plus;
#BindView(R.id.swipe_result_content)
public View swipeResultContent;
#BindView(R.id.item_content)
public View itemContent;
#BindView(R.id.swiped_text)
public TextView swipedText;
#BindView(R.id.swiped_action)
public TextView swipedAction;
public Runnable rSwipedActionRunnable;
public ViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
//this.view = view;// ?
swipedAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (rSwipedActionRunnable != null){
rSwipedActionRunnable.run();
}
}
});
}
}
}
And this is the XML-view of the List-Item:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="#dimen/material_drawer_item_primary">
<LinearLayout
android:id="#+id/swipe_result_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:visibility="visible"
android:paddingEnd="#dimen/material_drawer_vertical_padding"
android:paddingLeft="#dimen/material_drawer_vertical_padding"
android:paddingRight="#dimen/material_drawer_vertical_padding"
android:paddingStart="#dimen/material_drawer_vertical_padding">
<TextView
android:id="#+id/swiped_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_vertical|start"
android:lines="1"
android:singleLine="true"
android:textDirection="anyRtl"
android:textColor="#android:color/primary_text_dark"
android:textSize="#dimen/material_drawer_item_primary_text"
tools:text="Removed"/>
<TextView
android:id="#+id/swiped_action"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center_vertical|start"
android:lines="1"
android:singleLine="true"
android:textDirection="anyRtl"
android:textAllCaps="true"
android:textColor="#android:color/primary_text_dark"
android:textStyle="bold"
android:textSize="#dimen/material_drawer_item_primary_description"
android:text="#string/action_undo"/>
</LinearLayout>
<LinearLayout
android:id="#+id/item_content"
android:layout_width="match_parent"
android:layout_height="#dimen/material_drawer_item_primary"
android:orientation="horizontal"
android:paddingEnd="#dimen/material_drawer_vertical_padding"
android:paddingLeft="#dimen/material_drawer_vertical_padding"
android:paddingRight="#dimen/material_drawer_vertical_padding"
android:paddingStart="#dimen/material_drawer_vertical_padding">
<TextView
android:id="#+id/material_drawer_song"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="12dp"
android:layout_marginLeft="12dp"
android:lines="1"
android:singleLine="true"
android:textSize="#dimen/material_drawer_item_primary_text"
tools:text="Some drawer text" />
<TextView
android:id="#+id/material_drawer_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginLeft="12dp"
android:fontFamily="sans-serif"
android:lines="1"
android:singleLine="true"
android:textSize="#dimen/material_drawer_item_primary_description"
tools:text="Some counter text"
android:layout_weight="1"
android:gravity="center_vertical|start" />
<ImageView
android:id="#+id/material_drawer_minus"
android:layout_width="50dp"
android:layout_height="match_parent"
app:ico_color="#color/md_black_1000"
app:ico_icon="#string/gmd_remove_circle"
app:ico_size="50dp" />
<ImageView
android:id="#+id/material_drawer_plus"
android:layout_width="50dp"
android:layout_height="match_parent"
app:ico_color="#color/md_black_1000"
app:ico_icon="gmd-add_circle"
app:ico_size="50dp" />
</LinearLayout>
</FrameLayout>
</layout>
The part of the code managing the display of the undo button is inside the bindView() method of the ModelItemView.
Please ensure that after swiping the correct item is retrieved via the getItem(position) in the itemSwiped and ensure that the correct item gets notified via notifyItemChanged().
After that ensure that the bindView() is triggered again on that element, and that it has the proper swipeDirection as set via setSwipedDirection(direction).
This is important as:
viewHolder.swipeResultContent.setVisibility(iSwipedDirection != 0 ? View.VISIBLE : View.GONE);
viewHolder.itemContent.setVisibility(iSwipedDirection != 0 ? View.GONE : View.VISIBLE);
Is used to properly adjust the visibility of the views, including showing the undo button.
After several weeks, the answer is simple:
The undo-Button depends on the itemanimator which I always nullified to avoid the blinking. Here is a nice custom animator class that supresses whatever animation you dont want. Now all I had to do was
RecyclerView.setItemAnimator(new CustomItemAnimator());
Related
I'm also most done with my shopping list project but I encountered a problem. I need to get 2 pieces of info:
a bool to see if my radio button is checked;
a string of my location name.
The complication is that I want to update my SQLite DB using the ROOM architecture from my fragment. I am unable to perform an update to my DB as my fragment holds the reference to my ViewModel and my adapter holds the reference to my spinner and radio button. I want to be able to perform DB functions when my recyclerview appears in my fragment. I've tried interfaces and getter methods but can't seem to solve this problem. Please help!
here is my card layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:cardCornerRadius="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/textview_groceries_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLength="20"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:text="Sample Text"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
app:fontFamily="#font/quicksand_light"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/location_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample location"
app:layout_constraintStart_toStartOf="#id/textview_groceries_item_name"
app:layout_constraintTop_toBottomOf="#id/textview_groceries_item_name" />
<RadioButton
android:id="#+id/radio_full_trolley"
android:layout_width="45dp"
android:layout_height="40dp"
android:background="#drawable/trolley_cart_selector"
android:button="#drawable/full_shopping_cart_small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="#+id/spin_shop_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/radio_full_trolley" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
here is my recyclerview adapter:
public class GroceriesListAdapter extends RecyclerView.Adapter<GroceriesListAdapter.GroceriesItemHolder> {
private List<GroceriesListItem> ingredientsList = new ArrayList<>();
private List<String> shopNames = new ArrayList<>();
private ArrayAdapter<String> shopNameSpinnerAdapter;
#NonNull
#Override
public GroceriesItemHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//example list to fill spinner, will be changed in the future
shopNames.clear();
shopNames.add("SuperMarket");
shopNames.add("Green Grocer");
shopNames.add("Dollar Shop");
shopNames.add("Asian Market");
shopNames.add("Other");
View groceriesItemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardlayout_groceries_item, parent, false);
shopNameSpinnerAdapter = new ArrayAdapter<>(parent.getContext(), android.R.layout.simple_spinner_item, shopNames);
shopNameSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
return new GroceriesItemHolder(groceriesItemView);
}
#Override
public void onBindViewHolder(#NonNull final GroceriesListAdapter.GroceriesItemHolder holder, int position) {
GroceriesListItem currentGroceryItem = ingredientsList.get(position);
holder.textViewGroceriesItemName.setText(currentGroceryItem.getItemToBuy());
holder.placesSpinner.setAdapter(shopNameSpinnerAdapter);
}
#Override
public int getItemCount() {
return ingredientsList.size();
}
public void setGroceriesList(List<GroceriesListItem> ingredientsList) {
this.ingredientsList = ingredientsList;
notifyDataSetChanged();
}
class GroceriesItemHolder extends RecyclerView.ViewHolder {
private TextView textViewGroceriesItemName;
private Spinner placesSpinner;
private TextView textViewLocation;
private RadioButton trolleyCartStatus;
public GroceriesItemHolder(#NonNull View itemView) {
super(itemView);
textViewGroceriesItemName = itemView.findViewById(R.id.textview_groceries_item_name);
**placesSpinner = itemView.findViewById(R.id.spin_shop_name);**
textViewLocation = itemView.findViewById(R.id.location_textView);
**trolleyCartStatus = itemView.findViewById(R.id.radio_full_trolley);**
}
}
//this is for deleting the item in the fragment -- completed
public GroceriesListItem getListItemAt(int position) {
return ingredientsList.get(position);
}
}
Here is my fragment:
public class GroceriesListFragment extends Fragment {
private ScheduleViewModel scheduleViewModel;
private GroceriesListViewModel groceriesListViewModel;
private RecyclerView groceriesRecyclerView;
private List<String> listOfCurrentMeals = new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final GroceriesListAdapter groceriesListAdapter = new GroceriesListAdapter();
groceriesListViewModel = new ViewModelProvider(getActivity()).get(GroceriesListViewModel.class);
scheduleViewModel = new ViewModelProvider(getActivity()).get(ScheduleViewModel.class);
scheduleViewModel.getMealSchedule().observe(getViewLifecycleOwner(), new Observer<List<MealSchedule>>() {
#Override
public void onChanged(List<MealSchedule> currentMealSchedule) {
//get all meals into a list and remove Not decided entries -- completed
for (MealSchedule oneDaysMeals : currentMealSchedule) {
String breakfastDish = oneDaysMeals.getBreakfastMeal();
String LunchDish = oneDaysMeals.getLunchMeal();
String DinnerDish = oneDaysMeals.getDinnerMeal();
if (breakfastDish.equals("Not Decided")) {
continue;
} else {
listOfCurrentMeals.add(breakfastDish);
}
if (LunchDish.equals("Not Decided")) {
continue;
} else {
listOfCurrentMeals.add(LunchDish);
}
if (DinnerDish.equals("Not Decided")) {
continue;
} else {
listOfCurrentMeals.add(DinnerDish);
}
}
//get current Groceries list and refresh list if changed -- completed
if (listOfCurrentMeals.isEmpty()) {
Toast.makeText(getContext(), "No items to show", Toast.LENGTH_SHORT).show();
} else {
groceriesListViewModel.clearList();
for (String dishNames : listOfCurrentMeals) {
scheduleViewModel.getOneDishItem(dishNames).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableMaybeObserver<Dish>() {
#Override
public void onSuccess(Dish dish) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient1(), false, ""));
if (!dish.getIngredient2().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient2(), false, ""));
}
if (!dish.getIngredient3().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient3(), false, ""));
}
if (!dish.getIngredient4().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient4(), false, ""));
}
if (!dish.getIngredient5().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient5(), false, ""));
}
if (!dish.getIngredient6().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient6(), false, ""));
}
if (!dish.getIngredient7().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient7(), false, ""));
}
if (!dish.getIngredient8().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient8(), false, ""));
}
if (!dish.getIngredient9().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient9(), false, ""));
}
if (!dish.getIngredient10().isEmpty()) {
groceriesListViewModel.insertIngredient(new GroceriesListItem(dish.getIngredient10(), false, ""));
}
}
#Override
public void onError(Throwable e) {
Toast.makeText(getContext(), "Something went wrong", Toast.LENGTH_SHORT).show();
}
#Override
public void onComplete() {
}
});
}
}
}
});
//get list of ingredients and observe changes
groceriesListViewModel.getCurrentList().observe(getViewLifecycleOwner(), new Observer<List<GroceriesListItem>>() {
#Override
public void onChanged(final List<GroceriesListItem> groceriesListItems) {
groceriesListAdapter.setGroceriesList(groceriesListItems);
}
});
View groceriesListFragmentView = inflater.inflate(R.layout.frag_groceries_list, container, false);
groceriesRecyclerView = groceriesListFragmentView.findViewById(R.id.groceries_list_recyclerview);
groceriesRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
groceriesRecyclerView.setAdapter(groceriesListAdapter);
//swipe to delete function -- completed
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
groceriesListViewModel.deleteIngredient(groceriesListAdapter.getListItemAt(viewHolder.getAdapterPosition()));
}
}).attachToRecyclerView(groceriesRecyclerView);
//clear list function -- completed
final Button clearListButton = groceriesListFragmentView.findViewById(R.id.clear_list_button);
clearListButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
groceriesListViewModel.clearList();
}
});
//how to create a method with reference to spinner and radio button?
return groceriesListFragmentView;
}
}
I am using for loop, and I have around 20 items displaying in recycler view! Now how do I hide/show image view as for loop runs... Here tts is working fine, but when i try to show/hide using this its not happening..
Presently with below code, once for loops ends the entire imageview is affecting, i want it a row wis(show current(image1) & hide previous imageview(image) )
I am calling this method from main Activity class, but the imageview show or hide not happening
activity
......
......
private void ConvertTextToSpeech() {
// TODO Auto-generated method stub
//items.forEach( Multiples obj -> System.out.println());
int z=0;
View holder=null; ImageView imageView=null;ImageView imageView1=null;
for (Multiples p : items) {
if(z>0){
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.VISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.INVISIBLE);
}
if(z < items.size()) {
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.INVISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.VISIBLE);
}
text = p.first + " " + p.getSecond() + " Za "+p.getResult()+".";
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
z++;
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.balysv.materialripple.MaterialRippleLayout
android:id="#+id/lyt_parent"
style="#style/RippleStyleBlack"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:focusable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#color/grey_10" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="horizontal"
android:textAlignment="gravity" >
<View
android:layout_width="#dimen/spacing_large"
android:layout_height="wrap_content" />
<ImageView
android:id="#+id/image"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/transparent"
android:src="#drawable/ic_arrow_right" />
<ImageView
android:id="#+id/image1"
android:layout_width="51dp"
android:layout_height="35dp"
android:background="#android:color/transparent"
android:src="#drawable/ic_arrow_right"
android:visibility="invisible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="#dimen/spacing_middle"
android:paddingTop="#dimen/spacing_middle">
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/spacing_middle"
android:layout_marginRight="#dimen/spacing_middle"
android:fontFamily="sans-serif-condensed"
android:gravity="center|left"
android:text="36"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
android:textColor="#color/grey_80"
android:textSize="30sp"
/>
</LinearLayout>
<View
android:layout_width="#dimen/spacing_large"
android:layout_height="match_parent" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#color/grey_10" />
</LinearLayout>
</com.balysv.materialripple.MaterialRippleLayout>
</RelativeLayout>
adapter
public class AdapterListAnimation extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Multiples> items = new ArrayList<>();
private Context ctx;
private OnItemClickListener mOnItemClickListener;
private int animation_type = 0;
.........
.........
You need just add field in your adapter where you will save currently active item. Then call notifyDataSetChanged.
Also you should update your onBindViewHolder like this:
private int currentPosition = -1;
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.textView.setText(items.get(position));
if (currentPosition == position) {
holder.img1.setVisibility(View.INVISIBLE);
holder.img2.setVisibility(View.VISIBLE);
} else {
holder.img1.setVisibility(View.VISIBLE);
holder.img2.setVisibility(View.INVISIBLE);
}
}
public void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
I've created a test project to show how it can be implemented. Here you can see how it works. Here is my github repository.
You should not directly change the item view state inside the adapter with the following:
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.VISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.INVISIBLE);
instead, you need to use a specific method to tell the RecyclerView adapter that you need to change a state of an item view.
Be noted, that RecylerView naturally (in programmatically way) will recyler it's previous item whenever it needs to draw another item. So, you need to keep the state of each item whether using a specific variable inside your model or using a variable to hold each item state.
In case that you don't want to change your model, you can use SparseBooleanArray to hold the state. You can do something like this in your Adapter:
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Multiples> mItems;
// We use this to hold the image state.
private SparseBooleanArray mImageStates;
public YourAdapter(List<Multiples> items) {
this.mItems = items;
mImageStates = new SparseBooleanArray();
...
}
#Override
public YourAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
return viewHolder;
}
#Override
public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
int itemPosition = viewHolder.getAdapterPosition();
Multiples item = mItems.get(itemPosition);
// Load the state by the previous saved state
// default value is false.
if(mImageStates.get(itemPosition)) {
// show the images or something.
} else {
// hide the images or something.
}
}
public void setState(int position, boolean isVisible) {
mImageStates.put(position, isVisible);
}
}
now you can change the state item visibility with:
yourAdapter.setState(yourItemPosition, true);
then notify the adapter about the change with:
yourAdapter.notifyItemChanged(yourItemPosition);
or reset all if you have change many items with:
yourAdapter.notifyDataSetChanged();
you have to write a logic on onbindholder then.
onBindHolder(ViewHolder holder, int position){
if(position %2==0)
imageview.setVisibility(View.Visible);
else
imageview.setVisibility(View.InVisible);
}
Finally Got the Solution
I have added two parameters in your Multiples. In your loop when a condition is true, I am setting variable according to you have done with Image view. after I am notifying RecyclerView. so, a callback will come on onBindViewHolder. there I have added conditions for show/hide Image view.
suppose left Arrow is image & right Arrow is image1
NOTE: Android Default TextToSpeech does not provide any event listener for complete. So, I have tried a new library that is well maintained and provide Speech recognition and Text to speech functionality.
Gradle:
implementation 'net.gotev:speech:1.4.0'
Initialization:
To start using the library, you have to initialize it in your Activity.
public class YourActivity extends Activity {
private count = 0;
private maxCount = 0;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
Speech.init(this, getPackageName());
maxCount = items.size();
}
#Override
protected void onDestroy() {
// prevent memory leaks when activity is destroyed
Speech.getInstance().shutdown();
}
}
public class Multiples implements Serializable {
private Boolean isShowimage = false;
private Boolean isShowimage1 = false;
public Boolean getShowimage() {
return isShowimage;
}
public void setShowimage(Boolean showimage) {
isShowimage = showimage;
}
public Boolean getShowimage1() {
return isShowimage1;
}
public void setShowimage1(Boolean showimage1) {
isShowimage1 = showimage1;
}
}
private void ConvertTextToSpeech() {
if (count < maxCount) {
reset();
String text = items.get(count).first + " " + items.get(count).getSecond() + " Za " + items.get(count).getResult() + ".";
Speech.getInstance().say(text, new TextToSpeechCallback() {
#Override
public void onStart() {
Log.i("speech", "speech started");
items.get(count).setShowimage(false);
items.get(count).setShowimage1(true);
adapter.notifyDataSetChanged();
}
#Override
public void onCompleted() {
Log.i("speech", "speech completed");
ConvertTextToSpeech();
count++;
}
#Override
public void onError() {
Log.i("speech", "speech error");
}
});
}
}
private void reset() {
for (Multiples p : items) {
p.setShowimage(true);
p.setShowimage1(false);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Multiples item = items.get(position);
if (item.getShowimage()) {
holder.image.setVisibility(View.VISIBLE);
} else {
holder.image.setVisibility(View.INVISIBLE);
}
if (item.getShowimage1()) {
holder.image1.setVisibility(View.VISIBLE);
} else {
holder.image1.setVisibility(View.INVISIBLE);
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView,imageView1;
public ViewHolder(View view) {
super(view);
this.imageView = (ImageView) view.findViewById(R.id.imageView);
this.imageView1 = (ImageView) view.findViewById(R.id.imageView1);
}
}
I am creating Android app, using room database.
I have two tables DogsTable:
#PrimaryKey(autoGenerate = true)
int dog_id;
String dogName;
and CatsTable (both tables have constructor and getter methods ):
#PrimaryKey(autoGenerate = true)
int cat_id;
String catName;
1- How to display in one RecyclerView two different object type
ArrayList<DogsTable> dog_list;
ArrayList<CatsTable> cat_list;
I am getting the values of dog_list and cat_list from ViewModel Query as show in MainActivity.class.
2- How to fix getItemCount() method? I don't know how to return two different object cat_list.size(); and dog_list.size();
3- Also in onBindViewHolder() method I don`t know how to get cat_list values to display them in UI?
4- Another problem is in swapToDelete() Method in MainActivity.class, I can get the dog id to delete it, but I can not get the cat id to delete it, how can I get the cat id ?
5- How can I display (dog1,dog2 , dog3) as show in first image? (i inserted the value manually in the first image just to show how i want to display them )
Existing Output as below:
My code
MainActivity.java
public class MainActivity extends AppCompatActivity implements MainActivityAdapter.ItemClickListener {
MyViewModel viewModel;
MainActivityAdapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
Button btn_addDog, btn_addCat;
EditText et_addDogName, et_addCatName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
actionButton();
buildRecyclerView();
setUpViewModel_dogs();
swapToDelete_dog();
}
private void initViews() {
et_addDogName = findViewById(R.id.addDogNameET_xml);
et_addCatName = findViewById(R.id.addCatNameET_xml);
}
public void actionButton() {
btn_addDog = findViewById(R.id.AddDog_btn_xml);
btn_addCat = findViewById(R.id.AddCat_btn_xml);
btn_addDog.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
insertDog();
}
});
btn_addCat.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
insertCat();
}
});
}
private void buildRecyclerView() {
recyclerView = findViewById(R.id.recyclerView_id);
adapter = new MainActivityAdapter(this, this);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
// Query
public void setUpViewModel_dogs() {
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
viewModel.getAllDogs().observe(this, new Observer<List<DogsTable>>() {
#Override
public void onChanged(#Nullable List<DogsTable> dogsTables) {
adapter.setDog_list((ArrayList<DogsTable>) dogsTables);
}
});
}
public void setUpViewModel_cats(){
viewModel.getAllCats().observe(this, new Observer<List<CatsTable>>() {
#Override
public void onChanged(#Nullable List<CatsTable> catsTables) {
adapter.setCat_list((ArrayList<CatsTable>) catsTables);
}
});
}
// Add
public void insertDog() {
String dogName = String.valueOf(et_addDogName.getText()).trim();
DogsTable obj_dog = new DogsTable(dogName);
viewModel.insertDog(obj_dog);
Toast.makeText(this, "Dog Added", Toast.LENGTH_SHORT).show();
}
public void insertCat() {
String catName = String.valueOf(et_addCatName.getText());
CatsTable obj_cat = new CatsTable(catName);
viewModel.insertCat(obj_cat);
Toast.makeText(this, "cat Added", Toast.LENGTH_SHORT).show();
}
// Delete
public void swapToDelete_dog() {
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
List<DogsTable> dog_pos = adapter.getDog_list();
viewModel.deleteDog(dog_pos.get(viewHolder.getAdapterPosition()));
}
}
).attachToRecyclerView(recyclerView);
}
public void swapToDelete_cat() {
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int item_id = item.getItemId();
if (item_id == R.id.menu_add) {
Intent in = new Intent(this, Add.class);
startActivity(in);
}
return super.onOptionsItemSelected(item);
}
#Override
public void onItemClickListener(int pet_id) {
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/addDogNameET_xml"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="add Dog name" />
<Button
android:id="#+id/AddDog_btn_xml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/addCatNameET_xml"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="add Cat name" />
<Button
android:id="#+id/AddCat_btn_xml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingConstraints" />
</LinearLayout>
MainActivityAdapter.java
public class MainActivityAdapter extends RecyclerView.Adapter<MainActivityAdapter.MyViewHolder> {
Context mContext;
ArrayList<DogsTable> dog_list;
ArrayList<CatsTable> cat_list;
ItemClickListener mItemClickListener;
public MainActivityAdapter(Context context , ItemClickListener itemClickListener) {
this.mContext = context;
this.mItemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClickListener(int pet_id);
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main_adapter, viewGroup, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
DogsTable dog_pos = dog_list.get(position);
// CatsTable catsTable = cat_list.get(position);
holder.dogName.setText(String.valueOf(dog_pos.getDogName()));
// holder.catName.setText(String.valueOf(catsTable.getCatName()));
}
#Override
public int getItemCount() {
if (dog_list == null ) {
return 0;
} else {
return dog_list.size();
}
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener , ItemClickListener {
TextView dogName;
TextView catName;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
dogName = itemView.findViewById(R.id.dogName_xml);
catName = itemView.findViewById(R.id.catName_xml);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
int pet_id = dog_list.get(getAdapterPosition()).getDogs_id();
mItemClickListener.onItemClickListener(pet_id);
}
#Override
public void onItemClickListener(int pet_id) {
int pos = dog_list.get(getAdapterPosition()).getDogs_id();
mItemClickListener.onItemClickListener(pet_id);
}
}
public void setDog_list(ArrayList<DogsTable> dog_list) {
this.dog_list = dog_list;
notifyDataSetChanged();
}
public ArrayList<DogsTable> getDog_list() {
return dog_list;
}
public void setCat_list(ArrayList<CatsTable> cat_list) {
this.cat_list = cat_list;
}
}
activity_main_adapter.xml
<?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="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dogs: " />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/dogName_xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cats: " />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="#+id/catName_xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
To support different view types, RecyclerView.Adapter provides a useful method int getItemViewType(int position):
Return the view type of the item at position for the purposes of view recycling.
The default implementation of this method returns 0, making the assumption of a single view type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.
Then, in onCreateViewHolder you can see that a second parameter is int viewType which comes from the method int getItemViewType(int position). Based on that, you can instantiate a ViewHolder you need, e.g. DogViewHolder or CatViewHolder.
But what about storing multiple view models in a single adapter and defining which ViewHolder type should be actually instantiated? Here are two most popular approaches:
Declaring multiple containers for multiple types and defining a custom logic for getItemViewType method, e.g. all odd numbers will go in the dogs' list and even numbers will go in the cats' list (or any other method, but beware that you will have to cope with different lists' sizes and all the view types you need). Also, getItemsCount should be overriden appropriately (return list1.size() + list2.size + ... + listN.size();)
Put all the view models in a single list and perform some kind of attributes checks: either it will be some property or the type itself (not recommended for scalability reasons). Then your code will look like this:
public int getItemViewType(int position) {
CommonParentForUpcasting item = items.get(position);
if (item instanceOf Dog) { // or something like item.type == Animal.CAT
return R.id.holder_dog;
} else {
return R.id.holder_cat;
}
}
If you want to come up with a second solution, this solution should suit you well.
Also, make sure to check this StackOverflow answer.
maybe I'm tired but I miss something in what's happening in my code. I have an activity with fragments.
My activity HomeActivity calls a webservice to fill a List (my business object), it does this onResume().
My fragment WalletFragment has a mehtod refreshCardListView() that uses an adapter to display the list. It does this onResume().
What I was doing and that was working until now was:
HomeActivity.onCreate() displays WalletFragment,
WalletFragment.onResume() calls this.refreshCardList() but at this
instant cardList is null so the adapter displays nothing
HomeActivity.onResume() calls webservice, which on success calls
walletFragment.refreshCardList(), this time cardList has been filled
by the webservice so tid displays the list correctly.
Now I thout it was stupid to call refreshList twice so I tried to move the displayWalletFragment from HomeActivity.onCreate() to the success callback of the webservice and remove the call to refreshCardList from HomeActivity and leaving it only in WalletFragment.onResume(), so it'd go like this:
HomeActivity.onResume() calls webservice, which on success displays WalletFragment, WalletFragment.onResume() calls this.refreshCardList(), cardList having been filled by the webservice.
However at this point my adapter crashes, because parent.getWidth() == 0 (and I needed parent width to display card images).
I don't understad why, by moving this bit of code, the parent view would now not be initialized at this point, do you have an idea?
So this is the original code I used that is working, the only things I changed are removing displayWalletFragment(false) from onCreate and moving it in the success return of refreshCardList and removing walletFragment.refreshCardListView() from there.
HomeActivity.java
public class HomeActivity extends Activity {
CardService cardService = new CardService(this);
UserService userService = new UserService(this);
User user = null;
Set<WegaCard> userCards = null;
ProfileFragment profileFragment;
WalletFragment walletFragment;
/*
* Saving card images for performance concerns.
*/
Map<String, Bitmap> cardsImageBitmaps = new HashMap<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
initializeComponents();
styleCompnents();
displayWalletFragment(false);
}
#Override
protected void onResume() {
Log.d(this, "fired HomeActivity.onResume()");
super.onResume();
user = null;
userCards = null;
refreshUser(
new SingleResultCallback<User>() {
#Override
public void onResponse(User cards) {
Log.d(this, "user: " + user.toString());
HomeActivity.this.user = user;
profileFragment.refreshUser();
}
},
new ErrorCallback() {
#Override
public void onResponse(Throwable error) {
Log.e(this, "error: " + error.toString());
}
});
refreshCardList(
new SingleResultCallback<Set<WegaCard>>() {
#Override
public void onResponse(Set<WegaCard> cards) {
Log.d(this, "cards: " + cards.toString());
userCards = cards;
walletFragment.refreshCardListView();
}
},
new ErrorCallback() {
#Override
public void onResponse(Throwable error) {
Log.e(this, "error: " + error.toString());
}
});
}
private void refreshCardList(SingleResultCallback<Set<WegaCard>> successCallback, ErrorCallback errorCallback) {
Log.d(this, "fired HomeActivity.refreshCardList()");
// First empty list...
userCards = new LinkedHashSet<>();
// ...then fill it back
cardService.getCards(false, true, successCallback, errorCallback);
}
private void refreshUser(SingleResultCallback<User> successCallback, ErrorCallback errorCallback) {
Log.d(this, "fired HomeActivity.refreshUser()");
// First empty user...
userCards = new LinkedHashSet<>();
// ...then recreate it
userService.getUser(successCallback, errorCallback);
}
public void displayWalletFragment(boolean addToBackStack) {
displayFragment(WalletFragment.newInstance(), addToBackStack);
}
public void displayCardFragment(String cardNumber, boolean addToBackStack) {
displayFragment(CardFragment.newInstance(cardNumber), addToBackStack);
}
public void displayProfileFragment(boolean addToBackStack) {
displayFragment(ProfileFragment.newInstance(), addToBackStack);
}
private void displayFragment(HomeFragment fragment, boolean addToBackStack) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.home_fragment, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
public void profileEdit(View view) {
ActivityLauncher.getInstance().startProfileActivityForResult(this, ProfileActivity.ProfileEditMode.EDIT_PROFILE, user);
}
public Map<String, Bitmap> getCardsImageBitmaps() {
return cardsImageBitmaps;
}
}
WalletFragment.java
public class WalletFragment extends HomeFragment {
List<WegaCard> cardList;
ListView cardListView;
WegaCardAdapter adapter;
public static WalletFragment newInstance() {
WalletFragment fragment = new WalletFragment();
// Bundle args = new Bundle();
// fragment.setArguments(args);
return fragment;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activity.walletFragment = this;
cardListView = (ListView) getView().findViewById(R.id.fragment_home_wallet_list);
cardListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
activity.displayCardFragment(cardList.get(position).getCardNumber(), true);
}
});
}
#Override
public void onResume() {
Log.d(this, "fired WalletFragment.onResume()");
super.onResume();
refreshCardListView();
}
void refreshCardListView() {
Log.d(this, "fired WalletFragment.refreshCardListView()");
// First empty list...
cardList = new ArrayList<>();
cardList.addAll(activity.userCards);
adapter = new WegaCardAdapter(activity, R.layout.adapter_card_item, cardList);
cardListView.setAdapter(adapter);
getView().findViewById(R.id.fragment_home_wallet_empty).setVisibility(cardList.isEmpty() ? View.VISIBLE : View.GONE);
}
}
WegaCardAdapter.java
public class WegaCardAdapter extends ArrayAdapter<WegaCard> {
public WegaCardAdapter(#NonNull Context context, #LayoutRes int resource, #NonNull List<WegaCard> objects) {
super(context, resource, objects);
}
/**
* Data that should appear in the view should be added here
*/
private class ViewHolder {
ImageView imageView;
}
/*
* Note: using layout_height="match_parent" on the ListView helps Android calculate faster
* the elements that are displayed on screen, and prevent the array adapter for calling
* getView() too many times, thus improving the display speed
*/
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(this, "fired WegaCardAdapter.getView(" + position + ")");
ViewHolder holder = null;
WegaCard card = getItem(position);
LayoutInflater mInflater = (LayoutInflater) getContext().getSystemService(android.app.Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.adapter_card_item, null);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.adapter_card_image);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
Bitmap cardImage = null;
if (getContext() instanceof HomeActivity) {
Log.d(this, "In home activity \\o/");
Map<String, Bitmap> savedBitmaps = ((HomeActivity) getContext()).getCardsImageBitmaps();
if (savedBitmaps.containsKey(card.getCardNumber())) {
Log.d(this, "Found saved image, using it ^^");
cardImage = savedBitmaps.get(card.getCardNumber());
}
else {
Log.d(this, "Didn't found saved image éè building and saving it for later!");
cardImage = card.getRoundedScaledBitmap(getContext(), parent.getWidth());
savedBitmaps.put(card.getCardNumber(), cardImage);
}
}
else {
Log.d(this, "Not in home activity?");
cardImage = card.getRoundedScaledBitmap(getContext(), parent.getWidth());
}
holder.imageView.setImageBitmap(cardImage);
return convertView;
}
}
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<!-- Fragment will be displayed here -->
<LinearLayout
android:id="#+id/home_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</RelativeLayout>
fragment_home_wallet.xml
<?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="match_parent">
<LinearLayout
android:id="#+id/fragment_home_wallet_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<LinearLayout
android:id="#+id/fragment_home_wallet_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone">
<ImageView
android:src="#drawable/icon_card_white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.gaetanl.aspa.ui.component.StaticTextView
android:id="#+id/fragment_home_wallet_empty_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/defaultSpacing"
android:text="#string/dashboard_text_nocard" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/defaultSpacing"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.gaetanl.aspa.ui.component.StaticTextView
android:id="#+id/fragment_home_wallet_empty_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/dashboard_text_tap1" />
<ImageView
android:src="#drawable/icon_plus_white"
android:layout_width="#dimen/inlineIcon"
android:layout_height="#dimen/inlineIcon"
android:layout_marginLeft="#dimen/wordSpacing"
android:layout_marginRight="#dimen/wordSpacing" />
<com.gaetanl.aspa.ui.component.StaticTextView
android:id="#+id/fragment_home_wallet_empty_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/dashboard_text_tap2" />
</LinearLayout>
</LinearLayout>
<!-- Note: using layout_height="match_parent" on the ListView helps Android calculate faster
the elements that are displayed on screen, and prevent the array adapter for calling
getView() too many times, thus improving the display speed -->
<ListView
android:id="#+id/fragment_home_wallet_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
</LinearLayout>
I found the solution here: When should I get Width View in Fragment.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.post(new Runnable() {
#Override
public void run() {
// do operations or methods involved
// View.getWidth(); or View.getHeight();
// here
}
});
}
You can try calling just displayWalletFragment(false); on your success callback and move walletFragment.refreshCardListView(); to the onViewCreated() of your WallentFragment.
This will ensure the right sequence of your Fragment being created and the list being populated.
I have a custom list view with layout contain two layouts, called upper and bottom.
upper layout contain spinner, set and remove buttons.
bottom layout contain text view and back button.
By default bottom layout is in GONE state and when the user clicks on set button upper layout is GONE and bottom is VISIBLE (clicking on back button in bottom layout will return upper bottom back).
my problem is spinner value is disappear after specific flows:
Flow 1:
Add two items
Click on SET button on the first item
Remove the second item
Click on 'Back' button on the first item
Flow 2:
Click on SET
Rotate screen
Click on Back
Just to be clear, spinner values are exist and if drop it down you'll found the names (and I have android:configChanges="keyboardHidden|orientation|screenSize" in my manifest).
So, why spinner value is disappear after those flows ?
Here is my code:
Names.java
public class Names
{
private String name;
private int nameIndex;
private Boolean isNameOnTop;
public Names()
{
name = "";
isNameOnTop = true;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNameIndex() {
return nameIndex;
}
public void setNameIndex(int nameIndex) {
this.nameIndex = nameIndex;
}
public Boolean getIsNameOnTop() {
return isNameOnTop;
}
public void setIsNameOnTop(Boolean isNameOnTop) {
this.isNameOnTop = isNameOnTop;
}
}
MainActivity.Java
public class MainActivity extends Activity
{
ArrayList<Names> namesArray = new ArrayList<>();
ListView lvNames;
ListviewAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvNames = (ListView) findViewById(R.id.listView);
adapter = new ListviewAdapter(this, namesArray);
lvNames.setAdapter(adapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_add)
{
namesArray.add(new Names());
adapter.notifyDataSetChanged();
return true;
}
return super.onOptionsItemSelected(item);
}
}
ListviewAdapter.java
public class ListviewAdapter extends BaseAdapter
{
public Activity context;
public LayoutInflater inflater;
private ArrayList<Names> namesID;
private boolean isDeleted;
public ArrayList<Names> getNamesID() {
return namesID;
}
public void setNamesID(ArrayList<Names> namesID) {
this.namesID = namesID;
}
// Constructor
public ListviewAdapter(Activity context, ArrayList<Names> names)
{
super();
setNamesID(names);
this.context = context;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return getNamesID().size();
}
#Override
public Names getItem(int position) {
return getNamesID().get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
public class ViewHolder
{
RelativeLayout relativeLayout_Upper;
RelativeLayout relativeLayout_Bottom;
Spinner spNames;
Button btn_set, btn_remove, btn_back;
TextView tvChosen;
int index;
}
#Override
public View getView(final int i, View view, final ViewGroup viewGroup) {
final ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.listview_row, null);
holder.relativeLayout_Upper = (RelativeLayout) view.findViewById(R.id.lvRow_upper_layout);
holder.relativeLayout_Bottom = (RelativeLayout) view.findViewById(R.id.lvRow_bottom_layout);
holder.spNames = (Spinner) view.findViewById(R.id.spNames);
holder.btn_set = (Button) view.findViewById(R.id.btn_set);
holder.btn_remove = (Button) view.findViewById(R.id.btn_remove);
holder.btn_back = (Button) view.findViewById(R.id.btn_back);
holder.tvChosen = (TextView) view.findViewById(R.id.tv_chosen);
view.setTag(holder);
}
else
holder = (ViewHolder) view.getTag();
holder.index = i;
if (isDeleted)
{
holder.spNames.setSelection(getItem(holder.index).getNameIndex());
holder.tvChosen.setText("Chosen: " + getItem(holder.index).getName());
if (getItem(holder.index).getIsNameOnTop())
{
holder.relativeLayout_Upper.setVisibility(View.VISIBLE);
holder.relativeLayout_Bottom.setVisibility(View.GONE);
}
else
{
holder.relativeLayout_Upper.setVisibility(View.GONE);
holder.relativeLayout_Bottom.setVisibility(View.VISIBLE);
}
}
// pop spinner names
String[] names = new String[]{"Tom", "Ben", "Gil", "Adam", "Moshe", "Adi", "Michael", "Yasmin", "Jessica", "Caroline", "Avi", "Yael"};
final ArrayAdapter<String> spNamesAdapter = new ArrayAdapter<String>
(view.getContext(), android.R.layout.simple_spinner_dropdown_item, names);
spNamesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.spNames.setAdapter(spNamesAdapter);
holder.spNames.setSelection(getItem(holder.index).getNameIndex());
holder.spNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//holder.spNames.setTag(position);
getItem(holder.index).setNameIndex(position);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
holder.btn_set.setTag(i);
holder.btn_set.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getItem(holder.index).setName(holder.spNames.getSelectedItem().toString());
int position = (Integer) v.getTag();
holder.tvChosen.setText("Chosen: " + getItem(position).getName());
holder.relativeLayout_Upper.setVisibility(View.GONE);
holder.relativeLayout_Bottom.setVisibility(View.VISIBLE);
getItem(holder.index).setIsNameOnTop(false);
}
});
// remove
holder.btn_remove.setTag(i);
holder.btn_remove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (Integer) v.getTag();
namesID.remove(position);
notifyDataSetChanged();
isDeleted = true;
}
});
// back
holder.btn_back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.relativeLayout_Upper.setVisibility(View.VISIBLE);
holder.relativeLayout_Bottom.setVisibility(View.GONE);
getItem(holder.index).setIsNameOnTop(true);
}
});
return view;
}
}
activity_main.xml: contain ListView only.
upper_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/spNames" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SET"
android:id="#+id/btn_set" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="REMOVE"
android:id="#+id/btn_remove" />
</LinearLayout>
bottom_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Chosen:"
android:id="#+id/tv_chosen"
android:layout_marginRight="20dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BACK"
android:id="#+id/btn_back" />
</LinearLayout>
listview_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/lvRow_upper_layout">
<include
android:layout_width="fill_parent"
android:layout_height="wrap_content"
layout="#layout/upper_view"
android:id="#+id/includeRow_register"
android:layout_alignParentLeft="true"
android:layout_marginLeft="0dp"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp" />
</RelativeLayout>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/lvRow_bottom_layout"
android:visibility="gone">
<include
android:layout_width="fill_parent"
android:layout_height="wrap_content"
layout="#layout/bottom_view"
android:id="#+id/includeRow_showData"
android:layout_alignParentLeft="true"
android:layout_marginLeft="0dp"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp" />
</RelativeLayout>
</LinearLayout>
In your ListviewAdapter, change:
// back
holder.btn_back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.relativeLayout_Upper.setVisibility(View.VISIBLE);
holder.relativeLayout_Bottom.setVisibility(View.GONE);
getItem(holder.index).setIsNameOnTop(true);
}
});
to:
// back
holder.btn_back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.relativeLayout_Upper.setVisibility(View.VISIBLE);
holder.relativeLayout_Bottom.setVisibility(View.GONE);
getItem(holder.index).setIsNameOnTop(true);
notifyDataSetChanged();
// OR you can use this
// holder.spNames.requestLayout();
}
});
Whenever you click on "Add" in the method onOptionsItemSelected() you add an object new names() into the list, and in the Names class, the value of attribute name is set to ""
I guess that's why you getting an empty item in the spinner.