i have lots of adapters and views, viewsmodels and so on. Since its hard to maintain those i would like to use databinding and mvvm for that case. Now i tried to forward the item clicks into the viewmodel. Since its a recycleview i would lovely not loose the functionality to have less memory usage.
Currently i have a view (Activity) which sets the ViewModel. The ViewModel itself has an Adapter. The adapter has a Constructor which receives the viewModel and set this into the item.
The Item uses this to send the events back to the ViewModel. How does it affect the memory? Is there a better way doing this? I used RXJava before but this looks like the same concept, doesnt it? Here's my sample code (truncated).
View
public class ScenesFragment extends BaseFragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.scenesFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.scenes_fragment, container, false);
this.scenesListViewModel = new ScenesListViewModel(getContext());
this.scenesFragmentBinding.setViewModel(this.scenesListViewModel);
View view = this.scenesFragmentBinding.getRoot();
return view;
}
}
BaseLayout
<layout ... >
<data><variable name="viewModel" type=".viewmodel.ScenesListViewModel"/></data>
<android.support.v7.widget.RecyclerView
app:adapter="#{viewModel.adapter}"
app:layoutManager="#{viewModel.layoutManager}" />
</layout>
ViewModel
public class ScenesListViewModel extends Observable implements IViewModel {
public final SceneAdapter adapter;
private List<Scene> scenes = new ArrayList<>();
public ScenesListViewModel(#NonNull Context context) {
this.adapter = new SceneAdapter(context, scenes, this);
}
public void onRemoveClick(Scene scene) {
Timber.d("Clicked remove in the scene:" + scene);
}
}
Item Layout
<layout>
<data>
<variable name="scene"type=".model.Scene"/>
<variable name="viewModel" type=".viewmodel.ScenesListViewModel"/>
</data>
<ImageButton
android:id="#+id/sceneDelete"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="15dp"
android:layout_weight="1"
android:background="#null"
android:onClick="#{() -> viewModel.onRemoveClick(scene)}"
android:src="#drawable/ic_delete_forever_white_48px"/>
</LinearLayout>
</layout>
and finally the adapter which set the viewModel into the item.
Adapter
public class SceneAdapter extends RecyclerView.Adapter<SceneAdapter.BindingHolder> {
private Context context;
private List<Scene> scenes;
private ScenesListViewModel scenesListViewModel;
public SceneAdapter(Context context, List<Scene> list, ScenesListViewModel scenesListViewModel) {
this.context = context;
this.scenes = list;
this.scenesListViewModel = scenesListViewModel;
}
#Override public void onBindViewHolder(SceneAdapter.BindingHolder holder, int position) {
final Scene scene = scenes.get(position);
holder.binding.setScene(scene);
holder.binding.setViewModel(scenesListViewModel);
holder.binding.executePendingBindings();
}
Another way doing it is to set a Listener in the ViewModel, but this is more likely mvp then mvvm. I could also use RXJava again and create a Subject within the adapter, but i would like to solve it with the android on-board tools.
in your BindingHolder subclass implement onClickListener
class BindingHolder extends RecyclerView.ViewHolder
implements OnClickListener {
// code code code
}
#Override
public void onClick(View v) {
//code code code, use getAdapterPosition() to get the... adapter position
}
}
You can get the same ViewModel in the ViewHolder by using "by activityViewModels".
Related
I am aware of what to do normally by means of a GridLayoutManager but as I've been using Google's GithubBrowserSample (Java) but I am unable to replicate the process using this method.
Tried changing the xml's span count but i am using the androidx version and seemingly doesn't play ball
My Main Fragment:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
categoryViewModel = ViewModelProviders.of(this, viewModelFactory).get(CategoryViewModel.class);
refreshAndObserve();
CategoryListAdapter cvAdapter = new CategoryListAdapter(dataBindingComponent);
binding.get().categoryList.setAdapter(cvAdapter);
adapter = new AutoClearedValue<>(this, cvAdapter);
}
My Adapter:
public class CategoryListAdapter extends DataBoundListAdapter<Category, ListItemCategoryBinding> {
private final androidx.databinding.DataBindingComponent dataBindingComponent;
public CategoryListAdapter(DataBindingComponent dataBindingComponent) {
this.dataBindingComponent = dataBindingComponent;
}
#Override
protected ListItemCategoryBinding createBinding(ViewGroup parent) {
ListItemCategoryBinding binding = DataBindingUtil
.inflate(LayoutInflater.from(parent.getContext()), R.layout.list_item_category,
parent, false, dataBindingComponent);
return binding;
}
}
Thank you.
This can be done via the XML it seems:
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
I've got a RecyclerView backed by a Realm findAll(). I use a RealmChangeListener to notify the list about updates, and everything works remarkably well given the heavy use of the blunt instrument notifyDataSetChanged().
private RealmResults<Sale> allSales;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
....
// Update sales list whenever the AllSales result changes
allSales = getRealm().where(Sale.class).findAll();
allSalesListener = new RealmChangeListener<RealmResults<Sale>>() {
#Override
public void onChange(RealmResults<Sale> results) {
saleAdapter.notifyDataSetChanged();
}};
allSales.addChangeListener(allSalesListener);
....
However, I'd really like to have good MVVC structure, keeping all the Realm code in the ViewModel and out of my Fragments. The Realm examples don't do this. And probably for good reason -- I don't see an elegant way to notify the adapter appropriately of changes in the RealmResults. Databinding isn't there yet; it doesn't seem to support backing a RecyclerView with an ObservableCollection... and even if it did, a RealmResult isn't an ObservableCollection.
At this point, I'm thinking that I need to create a "ListChangedListener" interface in my Fragment, and manually maintain a collection of listeners for every List property in my ViewModel. But that seems like an awful lot of extra code just to maintain View/Model separation.
TLDR: I'm looking for an example of a Realm-backed ListView or RecyclerView with no Realm code whatsoever in the View code. Or even just reassurance that my custom "listener" interface is a good path forward.
UPDATE: I had somehow overlooked the RealmRecyclerViewAdapter. See my answer below.
The Realm library includes a RealmRecyclerViewAdapter base class, which I had somehow overlooked. No matter how good your MVVC intentions, the Adapter can't really be divorced from the model implementation, so it may as well be one that's intended for it.
Anyhow, it is very clean and compact. Do yourself a favour and review the example.
Here's a minimalist working implementation, with Android Databinding used for the row fields to make the Adapter and ViewHolder even cleaner and simpler:
private void setUpRecyclerView() {
// Called from your onCreateView(...)
recyclerView.setLayoutManager(new LinearLayoutManager(mainActivity));
recyclerView.setAdapter(new MyRecyclerViewAdapter(mainActivity, mainActivity.getDb().serialsRR));
recyclerView.setHasFixedSize(true);
}
public class MyRecyclerViewAdapter extends RealmRecyclerViewAdapter<Serial, MyRecyclerViewAdapter.SerialViewHolder> {
private final ActivityMain activity;
public MyRecyclerViewAdapter(ActivityMain activity, OrderedRealmCollection<Serial> data) {
super(activity, data, true);
this.activity = activity;
}
#Override
public SerialViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.serial_row, parent, false);
return new SerialViewHolder(itemView);
}
#Override
public void onBindViewHolder(SerialViewHolder holder, int position) {
SerialRowBinding rowBinding = holder.getBinding();
rowBinding.setSerial(getData().get(position));
}
class SerialViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
#Getter SerialRowBinding binding;
public SerialViewHolder(View view) {
super(view);
binding = DataBindingUtil.bind(view);
}
}
}
I have created a basic app using RecyclerView and CardView from get tutorials from websites.
App is working fine and I have some confusion.(I am showing my whole code here)
confusion is that how code works step by step. So please clear my concept on it.
Basic Structure of my App :
I have created a row_data_layout xml file to bind on recycler_view.
Created an Data class file (Here I have defined my variable that I used in App).
Created an Adapter file (here I want to clear how it works step by step first which class gets called and why?).
Bind Data to RecyclerView on MainActivity file.
row_data_layout.xml file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/CardView"
android:paddingBottom="16dp"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
</android.support.v7.widget.CardView>
Data Class File:
public class Data {
public String Name;
Data(String Name)
{
this.Name=Name;
}
}
Data_Adapter Class file:
public class Data_Adapter extends RecyclerView.Adapter<Data_Adapter.View_holder> {
List<Data> list = Collections.emptyList();
Context context;
public Data_Adapter(List<Data> list, Context context) {
this.list = list;
this.context = context;
}
#Override
public Data_Adapter.View_holder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_data_layout,parent,false);
View_holder holder=new View_holder(v);
return holder;
}
#Override
public void onBindViewHolder(Data_Adapter.View_holder holder, int position) {
holder.name.setText(list.get(position).Name);
}
#Override
public int getItemCount() {
return list.size();
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public class View_holder extends RecyclerView.ViewHolder{
CardView cv;
TextView name;
public View_holder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.CardView);
name = (TextView) itemView.findViewById(R.id.txt_name);
}
}
}
MainActivity File:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Data> data = fill_data();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
Data_Adapter adapter = new Data_Adapter(data,getApplicationContext());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
public List<Data> fill_data()
{
List<Data> data = new ArrayList<>();
data.add(new Data("Bred Pit"));
data.add(new Data("Leonardo"));
return data;
}
}
Once you have a basic understanding of how a RecyclerView.Adapter works, it would make sense to take a deeper dive into the documentation.
What the adapter does is keep a pool of inflated views (this can be as many different types of ViewHolder as you would like) that it populates with the data you supply. When the adapter does not have an empty view in the pool it creates a new one.
When a view is attached to the RecyclerView, it is removed from the pool, and when it is detached (scrolls beyond view, to some distance), it is added back to the pool of empty views--this is why it is important to reset everything when you populate your ViewHolders.
The onCreateViewHolder() function is where a new, empty view (wrapped by a RecyclerView.ViewHolder) is created and added to the pool.
The onBindViewHolder() function gets a view from the empty pool and populates this view using the data you supplied to the adapter.\
You can use the onViewRecycled() method to perform specific actions like setting an ImageView's bitmap to null (on detach) in order to reduce memory usage.
I don't normally override onAttachedToRecyclerView(), but if you need to do something specific when your adapter is associated with the RecyclerView, you would do it here.
Hi guys I'm trying to access few methods and variables of fragment(containing a recycler view) from the recycler views adapter class.. Simplest way is to pass in the fragment reference along with the adapter which creating it. But I dont think passing the full adapter reference which creating the adapter is a good approach.
I'm using RxJava in my project and tried a lot of things with PublishSubject like creating a Subject in adapter, calling its onNext which an event is performed and subscribe to that subject in the fragment but it didnt work out..
So any good approach will be highly appreciated.
TIA...
I'd suggest to introduce EventBus in your app - pretty elegant way of communication between different components of the app.
Then it'd look like:
Fragment:
public class MyFragment extends Fragment {
private EventBus eventBus = EventBus.getDefault();
RecyclerViewAdapter viewAdapter;
#Override
public void onAttach(Context context) {
super.onAttach(context);
eventBus.register(this);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_a, container, false);
if (viewAdapter == null) {
viewAdapter = new RecyclerViewAdapter(eventBus);
}
RecyclerView recyclerView = (RecyclerView)rootView.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(viewAdapter);
return rootView;
}
#SuppressWarnings("unused") // invoked by EventBus
public void onEventMainThread(final DataRefreshedEvent event) {
// Do something!
}
#Override
public void onDetach() {
eventBus.unregister(this);
super.onDetach();
}
}
Adapter:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
EventBus eventBus;
public RecyclerViewAdapter(EventBus eventBus) {
this.eventBus = eventBus;
}
void sentSomethingToFragment() {
eventBus.post(new DataRefreshedEvent());
}
.....
}
Event: public final class DataRefreshedEvent {}
And just a note - with Dagger, it'd look even better.
I hope, it helps
My suggestion is go with interface approach.
1. Create one interface.
2. Fragment should implement that interface.
3. Pass that interface reference to the adapter.
4. Call the interface method from adapter
So that way you can communicate between fragment and adapter.
I have used listview with entries attribute like below :
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:entries="#array/fi"/>
Now i am converting it to RecyclerView
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
I want to know whether we have android:entries attribute in RecyclerView? Or any other attribute instead of entries?
As correctly explained in the other answers there isn't an attribute to fill the RecyclerView from xml. However using the Android Databinding you can create a similar attribute quite easily:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:entries="#{#stringArray/fi}"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
</layout>
Here the binding adapter definition:
import android.databinding.BindingAdapter;
public class RecyclerViewBindings {
#BindingAdapter("entries")
public static void entries(RecyclerView recyclerView, String[] array) {
recyclerView.setAdapter(new SimpleArrayAdapter(array));
}
static class SimpleArrayAdapter extends RecyclerView.Adapter<SimpleHolder> {
private final String[] mArray;
public SimpleArrayAdapter(String[] array) {
mArray = array;
}
#Override
public SimpleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
return new SimpleHolder(view);
}
#Override
public void onBindViewHolder(SimpleHolder holder, int position) {
holder.mTextView.setText(mArray[position]);
}
#Override
public int getItemCount() {
return mArray.length;
}
}
static class SimpleHolder extends RecyclerView.ViewHolder {
private final TextView mTextView;
public SimpleHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(android.R.id.text1);
}
}
}
Then you have to use the DataBindingUtil methods for inflate the layout.
Inflate inside an Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingUtil.setContentView(this, R.layout.content_main);
}
Inflate inside a Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ContentMainBinding bindings = DataBindingUtil.inflate(inflater, R.layout.content_main, container, false);
return bindings.getRoot();
}
No. There is no such directly available attribute in RecyclerView. You have to do it from java code programatically using LayoutManager and RecyclerView.Adapter. Refer this answer.
REASON:
As we know, RecyclerView won't inflate until we set a LayoutManager to it. Also, LayoutManager is necessary to inflate individual item views of RecyclerView. These individual item views are retrieved from the RecyclerView.Adapter. So, until you set both the LayoutManager and RecyclerView.Adapter to RecyclerView, you can't use RecyclerView.
I hope this answer helps you.
I'm afraid this is not possible out of the box, you can extend the RecyclerView and define your own custom attribute which accepts a string array, then you would populate your RecyclerView Adapter with these values.
Check this link:
http://developer.android.com/training/custom-views/create-view.html#customattr
No,In android, Recyclerview doesn't hold android:entries or like attributes.
RecyclerView is successor of listview but still missing entries attribute and onclicklistener
here is android official documentation link
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
This site also describes about the android:entries attribute.
http://androidride.com/easiest-way-make-listview-android/