Currently we're making an Android library that contains an Activity
. We want to control the exact flow and the state of the activity, but give the user that implements the library control what the UI looks like. Meanwhile, we want to expose the least amount of internal classes.
The SDK user may decide where views are placed, sizes, colors; we decide on what happens on onClicks and provide the texts of TextViews.
The activity makes use of a Model-View-Intent pattern, so we want to expose the immutable state. Without adjustable UI, the Activity and all its' classes are internal. With adjustable UI, a lot more classes have to made public. This increases the risk of breaking changes on updates and exposes our logic.
To expose the UI, several solution were thought of:
Having a static callback on the activity that calls onCreate()
, so setContentView()
can be set, and calls render(state: State)
on every state change. Our classes are shielded as we like, but using a static for this is questionable.
Make the Activity open, so sdk users can subclass it. This means that every class used by the activity, has to be changed from internal to public.The classes which actually should be internal, will be hidden by obfuscating them with ProGuard.
It would be the nicest to pass a function in the Intent
, which to my knowlegde is not possible.
Pass a POJO were we define parameters such as background color, which is the most restricting to the sdk user and is not in consideration.
Which solution is the best? Is there another method than the ones we thought of?
The SDK user may decide where views are placed, sizes, colors; we decide on what happens on onClicks and provide the texts of TextViews.
I picture this like so:
State
, a ViewGroup
, and an Actions
object, which I'll explain later.State
the consumer is required to create several views and place them as they wish within supplied ViewGroup
.Actions.register*Button
methods.Now, how to pass this callback method to sour SDK?
It would be the nicest to pass a function in the Intent
This is actually possible with relative ease (and, IMO, some severe drawbacks).
In your SDK create a static Map<Key, Callback> sCallbacks
. When your consumer registers the callback using your API, you'll generate a lookup key for it and store it within the map. You can pass the key around as an Intent
extra. Once your SDK activity is opened it can lookup the callback using the key from its intent.
The key can be a String
or UUID
or whatever fits your needs and can be put inside an intent.
Pros:
Cons:
Activity
, which results in a memory leak.The calling point would look something like startSdk(context) { state, parent, actions -> /* ... */ }
.
This is by far the most comfortable method for the consumer, yet the weaknesses start to show once you leave the area of a typical consumer setup.
Make the Activity open, so sdk users can subclass it.
As you explained this is not possible without making compromies on your end.
Pros: ?
Cons:
AndroidManifest.xml
. I'm assuming the manifest merger is enabled. This is a pain, as I often forget to look here.The calling point would look something like startSdk<MySdkActivity>(context)
.
I really don't understand the benefits of this option, as a consumer. I lose the benefits of #1 and gain nothing in return. As a developer I can't sanction this 'let the consumer deal with it' attitude. They will break things, you'll get bug reports and you will have to handle it eventually.
Here I'll try to expand on the idea mentioned first in comments.
The callback would an abstract class defined by your SDK. It would be used as follows:
The callback class could have several methods, one could set up menu, one could setup only view hierarchy, one would get the whole activity as parameter. The consumer would pick the one they need. Again, this needs to be documented well if there are multiple options.
Pros:
AndroidManifest.xml
. This is great because registering your callback has nothing to do with Android. The manifest is mainly for things that the system interacts with.internal
.Cons:
I'm assuming you'll hide the intent passing as an implementation detail so the entry point could look something like startSdk<MyCallback>(context)
.
I like this one because it shifts all possible responsibilities from the consumer to you, the SDK developer. You make it hard for the consumer to use the API wrong. You shield the consumer from potential errors.
Now back to the first paragraph. As long as the consumer can get their hands on a context (ViewGroup.getContext()
) they're able to access the activity and the application (in that process). If both the calling activity and your SDK activity live in the same process the consumer could even access their prepared Dagger component. But they don't get to override your activity methods in unexpected ways.