Showing posts with label resources. Show all posts
Showing posts with label resources. Show all posts

23 August 2016

A big step

Today was the day.

Today was the day a thing I worked on was open sourced. It's done. It's out there. My code. For everyone to see. It is like baring my soul to the world. It is baring my soul to the world.

You can't possibly imagine how I feel. I am frightened. I am elated.

Will people find it useful? Will they use it? Will they look at my code and laugh at how stupid I am? Will they finally know for sure that I am a ruse? Will they whinge about how I am Not Doing Things Right?

And you know what? I don't care. I've done my part and did the best I could.

(I lied. I do care. But I promise I'll try to get over it as soon as I can.)

Be afraid. But still do it.

Massive thanks to the Domain Android team for the support, code reviews, suggestions, feedback, and putting up with my baking frenzies; and to Mike for the code contributions, and for helping me through the very arduous publishing process.

19 July 2016

Using resource IDs in data binding layouts

I have been playing with data binding more and more over the last couple of weeks. This week, it's all about creating a dialog with stuff dictated by a value from an enum.

Each value in this enum has the following properties:
@IdRes int textView
@StringRes int quote
@StringRes int name
@ColorRes int colour

I have added a new button in my Sandbox app to choose one value from the enum and display the properties in an alert dialog via data binding. Ambitious, I know!

The relevant Java part is mainly just creating and showing the dialog:
@OnClick(R.id.data_binding_alert)
public void onSendDataBoundAlert() {
   AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MaterialAlertDialog);
   builder.setPositiveButton(android.R.string.ok, null)
           .setNegativeButton("Not now", null);
   View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_data_binding_demo, null);
   DialogDataBindingDemoBinding binding = DataBindingUtil.bind(view);
   binding.setCharacter(getRandomCharacter());
   builder.setView(view);

   builder.create().show();
}

The tricky part is telling data binding how to use our resource IDs. Say I want the colour to be set as a background for a TextView, and the name to be displayed:
<TextView
     android:id="@+id/name"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="64dp"
     android:textSize="24sp"
     android:textStyle="bold"
     android:gravity="center_vertical"
     android:paddingLeft="16dp"
     android:background="@{character.colour}"
     android:text="@{character.name}"/>

Running the sample though, you'll see that the colour is not set and a dark grey-ish lilac-ish colour is used instead:


Unacceptable indeed!

So how do we make the Earl of Lemongrab happy? There are two ways of solving this problem.

A. Use a BindingAdapter

A BindingAdapter lets you manipulate and do a bit more involved logic on your data before applying it to the View. To use a BindingAdapter, first create a static method in your code that is bound to either a standard Android attribute or a custom one.

I create a custom attribute here called characterBackground:
@BindingAdapter({"characterBackground"})
public static void characterBackground(TextView textView, AdventureTimeCharacters character) {
     textView.setBackgroundColor(ContextCompat.getColor(textView.getContext(), character.getColour()));
}

You can then use this BindingAdapter in the TextView:
app:characterBackground="@{character}"
Do not forget to add the app namespace! Android Studio can add this for you. Just type in appNs and it will autocomplete.

This solution works, but is a bit too involved. And you said data binding is easy!!!

Well it kinda is, because, it turns out that we can:
B. Set the value in XML

But it didn't work! Well it turns out the problem is because we are passing a resource ID into an attribute that expects a resolved color value. So the only thing we need to do is to move all that code in our BindingAdapter into XML.

First add the import:
<data>
    . . .
    <import type="android.support.v4.content.ContextCompat" />
</data>

And then use it:
<TextView
     android:id="@+id/name"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="64dp"
     android:textSize="24sp"
     android:textStyle="bold"
     android:gravity="center_vertical"
     android:paddingLeft="16dp"
     android:background="@{ContextCompat.getColor(context, character.colour)}"
     android:text="@{character.name}"/> 

Very convenient! And it works! Note that you do not have to import anything to use context, there is some data binding magic going on that automatically injects it for you if you call it. Think of it as The Beckoning of the Context.


The Earl is now happy.

PS: If you need to set a drawable from a resource ID as an ImageView src, I found that you would also need to do this trick, aka:
android:src="@{ContextCompat.getDrawable(context, character.icon)}"


08 June 2014

Quick Tip: Understanding Alternate Resources

Trying to support as many devices as possible the best way possible is a very daunting task indeed. You will usually need to provide a lot of different layouts, strings, or dimensions (among others) to make your app look great whatever the user's device is. And then you start chaining resource qualifiers and testing which resource is being loaded by the OS can become a nightmare very quickly.

Here's a trick I started using which seemed to work quite well. Create a string (the app name works well if you have an Action Bar) that you can display on-screen (or just throw into a Log or a Toast) that will quickly let you know from which of those very many /res folders your resources are being pulled out of.

For this demo, I have the following /res/values-xxxxx folders:

As you can see, there are strings.xml files in each of the configurations I want to support. Each of these files will contain whatever description we want to see on the device or in the Logs. In my case, I have custom strings for each form factor and orientation, which allows me to validate things easier. But this is particularly useful if the changes per form factor and orientation is subtle, such as dimensions.

Here's what it looks like for phones:


And here are the outputs for tablets:

Each of the strings.xml files contains just these two strings (sample taken from /res/values-sw800dp):


    MultiDeviceSupport - Tablet Portrait
    Hello world on a tablet!


Have fun debugging!