Android Studio Tip #1 – Extract Constants

If you have hardcoded strings that should be extracted into constants, you can do that in at least 2 ways:

  • the hard way
  • the EASY way

The hard way  is to write yourself the constant and then replace it in the entire code where the string is used. And the EASY way  is to use the power of Android Studio :) So in order to extract a constant you have to do the following steps:

  • select the string that needs to be converted into a constant
  • use CTRL + ALT + C (on Windows) or Cmd + ALT + C (on Mac OS)

extract constant

Load localized strings at runtime

Here is a possible solution if you want to load localized text resources on a TextView in Android.

Problem:

You may need to extend your app flexibility to be able to load localized texts at runtime from a web server for example. That would be to be able to add new localizations (for new countries) without having to release a new update.

Solution:

One possible solution would be to:
1. Set a sort of naming conventions for your localized resources(text) like naming the key to something like: en_my_text, ro_my_text, de_my_text…
2. Load the text resources from the webserver and save those into a SharedPreferences file using the naming convention from above
3. Create a custom TextView and a custom attribute for the text view that will be used to set in XML the key for your localized text
4. Use the value from the custom attribute to load the translation on the TextView when Android loads your TextView

Simple sample

The xml attribute:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Define our custom attributes to be used on the TextView or where we need -->
    <declare-styleable name="LocalizationAttributes">
        <attr name="locText" format="string" />
    </declare-styleable>
</resources>

 

You can save that under res -> values -> attributes.xml

The TextView custom class:

/**
 * A simple TextView that has the ability to fetch a custom attribute (locText in our case) if is set into the xml
 * and use it as key to load a localized resource from the SharedPreferences (assuming that the localization was saved there at runtime)
 *
 * @author catalinprata
 */
public class LocalizedTextView extends TextView {

    public LocalizedTextView(Context context) {
        super(context);
        init(null, 0);
    }

    public LocalizedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public LocalizedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {

        // Load attributes
        final TypedArray typedArray = getContext().obtainStyledAttributes(
                attrs, R.styleable.LocalizationAttributes, defStyle, 0);

        // get the locText value that might be set in the xml
        String localizedString = typedArray.getString(
                R.styleable.LocalizationAttributes_locText);

        typedArray.recycle();

        if (localizedString != null) {

            setLocalizedText(localizedString);

        }

    }

    /**
     * Sets the text to the text being saved into SharedPreferences with the given key
     */
    public void setLocalizedText(String key) {

        SharedPreferences settings = getContext().getSharedPreferences("localization_strings_file", 0);

        // fetch the localized string with the given key (that is set in XML)
        // and set it to the text view, or set an empty String
        setText(settings.getString(key, ""));

    }

}

I uploaded the source project here for you.


 

Homework:

As an exercise and also improvement, you could create a new attribute called language that could store the language prefix in strings. This way you can remove the string you added in the xml and replace it with just the key for the resource.
After that you could fetch the value of that in your custom TextView and append the language prefix attribute value to the key attribute value to form the final key that you use to load the resource from the SharedPreferences.

This way you have to write only one string resource under the strings.xml file.

Cheers!

 

 

 

 

 

 

How to unzip Gzipped InputStream

If you are working with HttpURLConnection on Android then you might encounter strange InputStream outputs like when you have a strange character encoding even though on the server side everything seems ok.

A problem I was encountered lately was when I needed to parse a RSS/Atom feed on Android. I was getting the following error:

parse error : line 1 column 0 not well-formed (invalid token)

Well it seems that in some cases you might run into gzipped content that is sent from the webserver. That means that the server compresses the content in order to shrink the size of the file. This way the speed of the response is increased..

It seems that in this case HttpURLConnection doesn’t do the unzipping work for you so you have to do it yourself!

I noticed that there are some over the internet that are saying that you can also disable the GZip compression by setting the Accept-Encoding header to “identity” like so:

urlConnection.setRequestProperty("Accept-Encoding", "identity");

I wouldn’t recommend doing that unless you really need to. This is because GZip compression can speed up network communication and also save user’s band if it’s not on a wifi network.

Here is how to do it if you want to keep GZip compression:

public InputStream decompressGZipIfNeeded(InputStream input) throws IOException {
        InputStream decompressedStream;

        // we need only the first 2 bytes from the input stream
        byte[] magic = new byte[2];

        PushbackInputStream pushbackInputStream = new PushbackInputStream(input, 2);
        // read the first 2 bytes from the stream
        pushbackInputStream.read(magic, 0, 2);

        // reset the input stream so we can use it latter 
        pushbackInputStream.unread(magic);

        if (magic[0] == (byte) 0x1f && magic[1] == (byte) 0x8b) {
            // check if this is compressed with Gzip (see also GZIPInputStream.GZIP_MAGIC) and decompress the stream
            decompressedStream = new GZIPInputStream(pushbackInputStream);
        } else {
            // just return what we received
            decompressedStream = pushbackInputStream;
        }
        return decompressedStream;
    }

The code is not entirely mine, it is out there on the internet, I just added a few comments and minor renaming in order to be more readable and easy to understand.