I was wondering if someone found some clever solution to reuse components (multiple views) when working with MVVM.
By component I mean a set of views that end up being reused in an app.
For instance, an empty state formed of an ImageView and a TextView, and let's also add some sort of ClickListener for the text, for the sake of the example.
Now, what I want to do is to reuse this view in multiple .xml files BUT providing different values for the text, the image, and bind the listener to action in the Fragment's ViewModel.
What I've been doing is create a CustomEmptyState that would extend a LinearLayout or some kind of Layout and add Custom Attributes to it.
So, in the end, I would use my custom view like this:
<com.whatever.customViews.CutomEmptyState
app:image="#drawable/someImage"
app:text="#string/empty_text"
app:onTextClicked="#{viewModel.onEmptyStateClicked()}" />
My question would be, is there a different approach to this? A better one? What I dislike about this is writing the custom attributes with <declare-styleable> and all because then I have to keep track of 3 files:
The .xml layout of the base view
The .java/.kt of the view with the boilerplate code to handle the attributes
The <declare-styleable> with all the attributes
Is there any way to combine 2 and 3?
Say, you have to display some text value which you are sure will be databinded.
Then, if you databind the value, then there is a way, but not an elegant way.
declare a variable in the custom view like: private var status = ""
then write a setter function:
fun setStatus(status: String) {
this.status = status
//refresh your views based on value or set this to the text view
}
and then databind like this:
app:status="#{viewModel.status}"
so that you don't need to declare the stylable anymore
<com.whatever.customViews.CutomEmptyState
app:image="#drawable/someImage"
app:text="#string/empty_text"
app:onTextClicked="#{viewModel::onEmptyStateClicked}" />
public void onEmptyStateClicked(View view){
your code
}
Related
Do I have to rely on ListView/Recycler view each time I need to loop over data to repeat a layout ?
I totally understand it for long lists of data where scroll/performance is involved, but let's say I am sure i'll only have 0...3max items and need to display in very simple single-line-layout for each (1 image, 1 textview + button).. isn't there a simplier pattern than using adapters ?
Seems like overkill (and a pain to deal with for every little part of my screen where I need to loop overs small lists).
What are the other options while using Components architecture (databinding) ?
Manually inflating my layout ? In viewmodel ? fragment? Do I need to create another viewModel specially for this child layout ?
Thanks
I recently have a similar issue recently, but my problem was that of nested lists i.e. I needed to inflate another list inside a recycler view. Here is a minimal example of how I went about it.
Add a LinearLayout to your layout XML file:
<LinearLayout
android:id="#+id/smallList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:list="#{viewModel.yourList}" />
Create a binding adapter where you inflate the items. Like so:
#BindingAdapter("app:list")
fun setList(layout: LinearLayout, yourList: List<ListItemModel>) {
layout.removeAllViews() // Remove previous items if your list does change
for (listItem in yourList) {
ListItemBinding.inflate( // inflate your list item
LayoutInflater.from(layout.context),
layout, // pass your LinearLayout as root
true // attachToRoot is true so that the inflated view is added to the LinearLayout
).apply {
// set your binding variables
this.listItem = listItem
}
}
}
Note: This is a minimal example to solve the issue since actual data and functionality is unknown. You may want to:
Add a click listener variable to your list item XML file and set that similarly.
Create a custom view for the view if it is to be reused and write the binding adapter there.
I'm new to Android and have recently started adopting the pattern to create an auto-layout-loading custom view based on a layout file. In the layout you use the 'merge' tag as the root, then in the constructor of your view, you inflate that layout into yourself so you are essentially the root of the merged-in controls. Right after you inflate, still in the constructor, you can find the child controls from the layout and assign them to private fields in the custom class so they will always be available, even when using recycler views.
Contrast that with including a layout. In that case, the layout has to define a root element (which can be your custom control, but in that case, you would not inflate the layout into yourself and would have to move the control lookup to onFinishInflate) but otherwise it ends up with the same view hierarchy.
In short, instead of this...
<include layout="#layout/layout_myCustomControl" />
You can now do this...
<com.mydomain.MyCustomControl />
However I find the auto-loading custom control version to be much more flexible and maintainable. The advantages of the second one are not only that you can use custom attributes in the referencing XML layouts (which you can't with the 'include' version) but it also gives you a central place to manage the code as well as layout management/control lookup.
So now I can do this...
<com.mydomain.MyCustomControl
app:myCustomAttribute="Foo" />
And if the control has to have code backing up its behavior, it's nicely encapsulated in the custom view.
Here's an example layout for the auto-loading version...
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is static text in the header" />
<TextView android:id="#+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</merge>
And here's the class that uses it...
public class MainHeader extends LinearLayout
{
TextView dateTextView;
public MainHeader(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
setOrientation(VERTICAL);
LayoutInflater.from(context).inflate(R.layout.header_main, this);
dateTextView = (TextView)findViewById(R.id.dateTextView);
dateTextView.setText(<<Code for setting date goes here>>);
}
}
But what I'm wondering is if the layout is purely static in nature--say holding a static image and fixed text--is it still considered good practice to use a custom view to represent it? I'd say yes for consistency, plus should I want to extend it in the future, you're already prepared for it and have to do so in just one place regardless of how many places it's used. In contrast, if you included the layout directly in say 20 places, you may have to update all 20 (depending on what's actually changed/needed.)
Pros for the Custom View approach:
Central location for managing layout loading and control lookup
Can support backing code implicitly/internally for updating the view.
Can use attributes when being referenced in other layout files
Better encapsulation (you can hide the layout itself from the outside world exposing behaviors via direct methods)
Can simply be 'new'd' up in code and the layout will automatically load. No inflating or casting needed.
Cons for Custom View approach
When used in Layout files, you now have to also specify width and height making usage a little more verbose.
May add extra classes to your project (not always, but sometimes.)
Again, it seems to me like a no-brainer, but I'm wondering if there's a preferred 'Androidy' way, or is it just a preference up to each developer?
Such approach will add additional level in your view trees. So you only made the UI tree more complex for no good reason. From other side, you can follow next steps to get rid of that side-effect:
<merge /> can only be used as the root tag of an XML layout
when inflating a layout starting with a <merge />, you must specify a parent
ViewGroup and you must set attachToRoot to true (see the
documentation of the inflate() method)
That's it.
Well apparently what I came up with wasn't that unique after all! Here's an article explaining in detail this exact scenario, along with the pros and cons of each. Looks like they too came to the same conclusion.
TrickyAndroid: Protip. Inflating layout for your custom view
So, what I'd like is: defining a component, which includes TextView-s and an ImageView. This is an item, which I'd like to add to a (for example Linear) layout, so I can display all the custom items, I added one after the other.
The point is, that these items have to be editable, because a database query result will define their text content and the image.
Your custom component should be a ViewGroup itself. You can add any number of TextViews and ImageViews to it, and access them by their ID.
MyCustomViewGroup component = (MyCustomViewGroup)linearLayout.findViewById(...);
TextView textView1 = (TextView)component.findViewById(...);
ImageView imageView = (ImageView)component.findViewById(...);
You can go for the XML approach for defining your component. For instance, you can define your component as a LinearLayout, then add all the elements (TextViews, ImageViews) you need to that layout.
As for the "editable" part, just provide your elements with an id property
android:id="#+id/my_view"; that way you may obtain them through a findViewById(R.id.my_view) call.
You cast the results to whatever View implementation you are expecting, then change it's text/content/image with the interpreted results from your query.
I have a layout for a row to be used in a ListView. The layout contains about eight TextViews.
If a row represents data of type "A", I want it to have a red background with light grey font color for all TextViews. If the data is of type "B", I want the background color to be green and the font color to be black.
Right now, I have a custom ListAdapter and in the getView() method I'm programmatically changing the font color for each TextView, but that is very tedious.
I considered defining styles for "A" and "B", but there doesn't seem to be a way to apply a style programmatically (i.e. it can only be done statically in the XML layout).
I also considered having different layouts for "A" and "B", but I don't want to have duplicate code in two XML files.
Any ideas on a better way to do this?
This post show how to have alternate row colors.
Basically you implement getview such that you modify some UI components based on your conditions.
You can modify the code from the post for your use.
I couldn't find a decent way of doing this. Luckily, I am using the Butter Knife library and could use the View Groups feature:
You can group multiple views into a List or array.
#Bind({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<TextView> nameViews;
Action and Setter interfaces allow specifying simple behavior.
static final ButterKnife.Setter<TextView, Integer> SET_COLOR = new ButterKnife.Setter<TextView, Integer>() {
#Override
public void set(TextView view, Integer value, int index) {
view.setTextColor(value);
}
};
The apply method allows you to act on all the views in a list at once.
ButterKnife.apply(nameViews, SET_COLOR, ContextCompat.getColor(getContext(), android.R.color.primary_text_light));
As mentioned in topic, I have some Views, e.g. a TableRow with always the same background used as topic, or a special TableRow containing a TextView with some special styles/properties. These Views are set dynamically, so it's problematic to use a XML for this. As I read it's not possible to set styles programmatically too. So what's the best way to solve that?
Possibility 1:
I use and instance derived Views, like this:
public class TopicTableRow extends TableRow {
public TopicTableRow(Context context) {
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setBackgroundColor(Color.parseColor("#777777"));
setClickable(false);
}
}
Possibility 2:
I could create a valid xml template with a special layout I never use in the application, containing the needed Views which have already all assigned styles. Afterward I access the needed Views by R.id....
But this method seems to be very dilettante to me.
I don't think that those 2 possibilities are the "real" Android way to do this, so how is this usually done?
If you want to set specific styles for groups of elements, you can use the themes and styles concepts in android.
You can read up on them here: http://developer.android.com/guide/topics/ui/themes.html
It is not possible though to change the style attribute of a view programatically.
Therefore the android way is probably to create the Views you need in XML and use a LayoutInflater to get create an 'java' version of the xml view. This allows you to reuse the component and fill it with apropriate data for as many rows as you would like.
Button view = (Button) LayoutInflater.from(this).inflate(R.layout.textViewFromWeb, null);
I hope this will be of use to you!