5

I have started implementing Android data-binding library to my new app. But, I am having some difficulties with data dynamically added elements. In my POJO it contains a map of <String,Double>. Here, String is the id of a user and Double is the amount. I have a layout file for displaying single entry. So, if map contains 2 elements, it will looks like this:

enter image description here

Previously, I have done this by inflating layouts inside loop and adding to LinearLayout for each item of a map. But, now I want to do this with data-binding.

As, the number of elements in the Map can be anything from 1 to 20, I can not add in the layout file by default. I have to inflate as per the entries in the map. I have successfully implemented data-binding with the POJO without a Map. But, unable to understand how this can be done with data-binding easily.

So, Is there any way to do this directly in the data binding (in layout file) or with as little code possible in java code?

Edit: For all, who sees that it is a simple RecyclerView, it is not. I agree RecyclerView is very good choice when you have a list with some elements and scrolling that views will be performance benefit. Here I have multiple elements of different types above and below this list. I have used RecyclerView with data-binding also and I know how it works, but here it is not the case. I just want to inflate multiple rows inside a layout (LinearLayout) using data-binding.

kirtan403
  • 7,293
  • 6
  • 54
  • 97
  • Did you try `RecyclerView`? – Faraz Sep 05 '16 at 08:03
  • 1
    I don't need RecyclerView, there will be upto 5-7 elements most of the time. RecyclerView will be useful when there are many elements with scrolling – kirtan403 Sep 05 '16 at 08:06
  • In the question you said 1 to 20. And if in case, number of elements increased to >7, to be on safe side I suggest using `RecyclerView`. And your binding problem will be solved too. If you add all elements in `LinearLayout` programatically, and on screen rotation, you have to scroll to see all elements, which will cause memory issues here. – Faraz Sep 05 '16 at 08:14
  • Definitely you should use `RecyclerView` – Reaz Murshed Sep 05 '16 at 08:26

3 Answers3

9

Since you prefer to create the layout dynamically yourself, I'd propose the following approach. It's completely theoretical at the moment, but I don't see a reason, why this should not work for you.

Considering you set the Map with a custom attribute for binding.

<LinearLayout ...
    app:inflateData="@{dataMap}" />

You then have to create a @BindingAdapter to handle what the adapter would do for you.

@BindingAdapter({"inflateData"})
public static void inflateData(LinearLayout layout, Map<String, Double> data) {
    LayoutInflater inflater = LayoutInflater.from(layout.getContext());
    for (Entry<String, Double> entry : data.entrySet()) {
        MyItem myItem = inflater.inflate(R.layout.my_item, layout, true);
        myItem.setKey(entry.getKey);
        myItem.setValue(entry.getValue);
    }
}

Here you inflate the layout for the item and add it to the parent layout. You could generalize it even more by adding more attributes to the layout and binding adapter.

tynn
  • 38,113
  • 8
  • 108
  • 143
  • Excellent! This should work. I am going to give this a spin. Thanks! – kirtan403 Sep 05 '16 at 13:22
  • how to add myItem to parent layout? I know about addView() method, but the question is how to access parent layout to call addView() method over it? – Mihir Patel Apr 27 '19 at 22:48
  • 1
    Hi, its very nice answer, but i have some doubt inflate returns a view's objects and i am not able to find setKey and setValue methods , like :myItem.setKey – Geet Thakur Mar 13 '20 at 10:10
2

That's not what data bindings are used for. These are there to bind data to a statically defined layout. Creating or managing view hierarchies might be possible, but also a lot more complicated than just using RecyclerView with its adapters.

If you like it better to just set the data from the layout, you can write Custom Setters for the Data Binding Library and create the adapter in this scope instead.

@BindingAdapter({"adapter"})
public static void bindAdapter(RecyclerView view, Map<String, Double> data) {
   RecyclerView.Adapter adapter = ...;
   view.setAdapter(adapter);
}
tynn
  • 38,113
  • 8
  • 108
  • 143
  • Yes, I know it is used for statically defined layout. In my layout I am binding a lot of properties above and below this small list using the data binding. But, just for this part I was stuck. And didn't found anything how to do that. But, I think there is no need for the recycler view here. – kirtan403 Sep 05 '16 at 10:09
1

To make the data to be added/removed in dynamic way, you should use the Adapter instead of add it using your way. As google suggested, RecycelerView have better performance in UI and memory control. I have made some simple code which might suit to you.

For the Adapter, and the binding method.

public class MyOwnBindingUtil {
    public static class Holder extends RecyclerView.ViewHolder {
        private ItemBinding mItemBinding;
        public Holder(ItemBinding itemView) {
            super(itemView.getRoot());
            mItemBinding = itemView;
        }
    }
    public static class OwnAdapter extends RecyclerView.Adapter<Holder> {
        private Map<String, String> mMap;
        private List<String> keys;
        private List<Double> values;
        public OwnAdapter() {
            keys = new ArrayList<>();
            values = new ArrayList<>();
        }
        public void add(String key, Double value) {
            keys.add(key);
            values.add(value);
        }
        @Override
        public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
            ItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item, parent, false);
            return new MyOwnBindingUtil.Holder(binding);
        }
        @Override
        public void onBindViewHolder(Holder holder, int position) {
            holder.mItemBinding.setKey(keys.get(position));
            holder.mItemBinding.setValue(String.valueOf(values.get(position)));
        }
        @Override
        public int getItemCount() {
            return keys.size();
        }
    }
    @BindingAdapter("data:map")
    public static void bindMap(RecyclerView pRecyclerView, Map<String, Double> pStringStringMap) {
        pRecyclerView.setLayoutManager(new LinearLayoutManager(pRecyclerView.getContext()));
        OwnAdapter lAdapter = new OwnAdapter();
        for (Map.Entry<String, Double> lStringStringEntry : pStringStringMap.entrySet()) {
            lAdapter.add(lStringStringEntry.getKey(), lStringStringEntry.getValue());
        }
        pRecyclerView.setAdapter(lAdapter);
    }
}

There is some problem if you insist to use Map instead of list for the data since Map is not indexed but the adapter gets Data based on the index of the dataset.

https://www.mkyong.com/java8/java-8-convert-map-to-list/

How to convert a Map to List in Java?

How does one convert a HashMap to a List in Java?

In your xml, you can set your map to the recyclerview and your dataset can be set in xml.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:data="http://schemas.android.com/tools"
    >


    <data>

        <import type="java.lang.String"/>

        <import type="java.util.Map"/>

        <variable
            name="map"
            type="Map&lt;String, String&gt;"/>
    </data>

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        >

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            data:map="@{map}"/>

    </LinearLayout>
</layout>

In your activity code,

    Map<String, String> lStringStringMap = new HashMap<>();
    lStringStringMap.put("A", "A1");
    lStringStringMap.put("B", "B1");
    lStringStringMap.put("C", "C1");
    mBinding.setMap(lStringStringMap);
Community
  • 1
  • 1
Long Ranger
  • 5,888
  • 8
  • 43
  • 72
  • Thanks for the suggestions. I agree RecyclerView is very good choice when yo have a list with some elements and scrolling that views will be performance benefit. But, using RecyclerView will not be a good choice here, as I want some views (usually around 5-7) inline with lots of different views above and below this list. I have used data-binding with recycler views and it simply works fine. But here, it is not required. – kirtan403 Sep 05 '16 at 10:01
  • That's mean you have binding1,binding2,binding3... but not more than 7. And 7 bindings (if it s 7 ) are totally different layout. You want to add it in to the single container(may be it is LinearLayout). And now you want to find a easier way to add 7 different layout inside the single container instead of adding it one by one using data binding ? – Long Ranger Sep 05 '16 at 10:11
  • Nop, not like that. Let me explain. Say I have a POJO with this many elements: title, description, owner, created_at, created_by, modified_at, modified_by, icon, category, tag, location, **participants**. So, with this POJO and data-binding, I can set everything with ease. But, that "participants" part is a the tricky map. For just that map, I want to display a row for each which is fairly small in size. – kirtan403 Sep 05 '16 at 10:20