Skip to content
This repository has been archived by the owner on Feb 12, 2020. It is now read-only.
Santi Aguilera edited this page Dec 29, 2017 · 15 revisions

Loquacious

Loquacious is a library for storing localized resources.

Loquacious requires only a minimum api level of 14.

This library is born as a proof of concept, aiming to decrease the size of android applications. For this, this library aims at:

  • Reducing the .arsc symbol table. Instead of having a lot of localization files with strings, they are retrieved on demand. Making the .arsc only have 1 symbol per entry
  • Reducing the res/ files. As now you should only define the resources, but empty (they will get downloaded at a later stage)
  • Keeping the developer ways of coding intact, you will still reference them as R.whatever.whatever

Please note that there are still features unimplemented (although the objective is to have them), such as multiple decentralized stores. In the Future section I describe the missing ones.

Installation

Add it to your dependencies build.gradle

compile 'com.saantiaguilera.loquacious:loquacious:latest.version'
compile 'com.saantiaguliera.loquacious:locales:latest.version'
compile 'com.saantiaguilera.loquacious:etc:latest.version' // Unimplemented

Usage

Initialization

First, initialize Loquacious.

Loquacious.initialize(context)
  .with(LocaleStore(context))  // Implemented
  .with(dimensionStore)        // Unimplemented
  .with(imagesStore)           // Unimplemented
  ...

Careful this should only be done once!

Adding elements

Forget about which locale you are currently on, simply create the element you would like to save, and commit it to the resources. This will save it for the current system locale

// A string:
Loquacious.resources.put(Item(R.string.string, "string says whatever"))
// An int:
Loquacious.resources.put(Item(R.integer.int, 1234))
// A plural of type 'two'
Loquacious.resources.put(Item(R.plurals.plural, "plural for TWO", 2))

Or in batches

Loquacious.resources.putAll(items)

Since your server wont probably know whats the int value of R.string.whatever, you should rely on Resources#getIdentifier(String,String,String), and map the name your server gives you with its integer value.

Retrieving a resource

As you would do normally today on android, but simply use our resources! The methods are exactly the same, so dont worry about anything:

Loquacious.resources.getQuantityString(R.plurals.whatever, 2)
Loquacious.resources.getString(R.string.whatever)
Loquacious.resources.getDimensionPixelSize(R.dimen.whatever) // No idea why would you store a dimension localized.. But you can.

Clearing stores

Loquacious.resources.clear()

And we will clear all the stores

Using context resources

If you wish, you can override activity / application / whatever context wrapper you use and do:

override fun attachBaseContext(base: Context?) = super.attachBaseContext(Loquacious.wrap(base!!))

You should do this after initialization!

This will make the context.resources be the same as Loquacious, thus you can avoid the boilerplate of calling Loquacious.resources :)

Generics

If you haven't noted, the Item class provides whichever type of value you'd like for storing. This means you can store whichever DTO / object you want in a localized way if you want to.

Loquacious.resources.put(Item(R.string.dto, MyDto(...))
Loquacious.resources.get<MyDto>(R.string.dto)

Locale API

The locale api currently supports every type of resource except drawable, as it stores in plain text (and we wont do ultra ugly base64 transformations)

Subscribing to locale changes

Of course, you should have an easy way to know when the locale changed, so you can retrieve the new strings.. Since I already have to detect this I provide a simple interface for subscribing to this events. Watchout I keep them as strong references!

Loquacious.instance.subscribe { locale: Locale ->
  Log.w("Test", "Locale changed to $locale!")
}

And of course, you can unsubscribe them with Loquacious#unsubscribe((Locale) -> Unit)

Advanced

Implementing a custom storage

Of course, you can implement your own storage! Simply implement the Store class and when initializing add it with .with(store) :)

Storing in a specific storage

If you'd like to store something in a specific storage (else we will find the first that fits that type of resource), you can do it specifying it:

Loquacious.resources.put(Item(R.string.hello, "Hej!"), LocaleStore::class)

Since it would be tedious to keep all instances of the created stores, and there can only be one for each type, just specify the ::class and we will do the rest.

Retrieving from a specific storage

Pretty much the same deal as storing

Loquacious.resources.get(R.string.hello, LocaleStore::class)

Clearing a specific store

Again, pretty much the same as getting or putting:

Loquacious.resources.clear(LocaleStore::class)

Proguard

Loquacious already supports proguard transitively. So no need to add custom rules :)

Future enhacements

  • Create other stores (supporting screen size changes || dpi changes || drawables support || raw files support)
  • Add a loadResources from raw, with this:
    • We can have defaults
    • We can remove them, lets say the terms&conditions once they have agreed.
    • We avoid the network latency it imposes on those resources

Examples

In the demo-app you can find a simple example to retrieve a "Hello World" string in the language the phone sets, you'll realize that even though we get the resource as a normal developer does, theres no string written in the resources xmls

Working with this repo

If you would wish to check out the demo application, here are the steps to make it work:

  1. Clone the repository
  2. Run ./gradlew server:run on a console. Leave it as it is, since it will be running the server
  3. Run the demo-app in an emulator (since I have retrofit configured with the IP of a localhost redirect emulator)
  4. Use it.