- Notifications
You must be signed in to change notification settings - Fork25
Composition over inheritance for Android components like Activity or Fragment
License
passsy/CompositeAndroid
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Composition over inheritance
Allows to add functionality into an AndroidActivity
. Just because we all have aBaseActivity
in our projects containing too much unused stuff. When it grows, it get unmaintainable.
- Plugin for the GooglePlayApiClient handling all the edgecases
- Wrap your layout in a predefined container by overriding
setContentView()
- a Plugin showing a loading spinner
- a Plugin for requesting permissions and automatically handling all response codes
- gradually add libraries likeMosby (without extending from a
MvpActivity
) orFlow to your Activities when you need it - andso much more...
Given you have anActivity
showing a list of tweets (TweetStreamActivity
) and you want add view tracking.
You could do it with inheritance and useTrackedTweetStreamActivity
from now on:
publicclassTrackedTweetStreamActivityextendsTweetStreamActivity {@OverrideprotectedvoidonResume() {super.onResume();Analytics.trackView("stream"); }}
more likely you would create aTrackedActivity
and extend theTweetStreamActivity
from it:
publicabstractclassTrackedActivityextendsAppCompatActivity {publicabstractStringgetTrackingName();@OverrideprotectedvoidonResume() {super.onResume();Analytics.trackView(getTrackingName()); }}
publicclassTrackedTweetStreamActivityextendsTrackedActivity {@OverridepublicStringgetTrackingName() {return"stream"; }}
Both solutions work but don't scale well. You'll most likely end up with big inheritance structures:
classMvpActivityextendsAppCompatActivity { ... }classBaseActivityextendsAppCompatActivity { ... }classBaseMvpActivityextendsMvpActivity { ... }classWizardUiActivityextendsBaseActivity { ... }classTrackedWizardUiActivityextendsWizardUiActivity { ... }classTrackedBaseActivityextendsBaseActivity { ... }classTrackedMvpBaseActivityextendsBaseMvpActivity { ... }
Some libraries out there provide both, a specializedActivity
extendingAppCompatActivity
and a delegate with a documentation when to call which function of the delegate in yourActivity
.
publicclassTrackingDelegate {/** * usage: * <pre>{@code * * @Override * protected void onResume() { * super.onResume(); * mTrackingDelegate.onResume(); * } * } </pre> */publicvoidonResume() {Analytics.trackView("<viewName>"); }}
publicclassTweetStreamActivityextendsAppCompatActivity {privatefinalTrackingDelegatemTrackingDelegate =newTrackingDelegate();@OverrideprotectedvoidonResume() {super.onResume();mTrackingDelegate.onResume(); }}
This is an elegant solution but breaks when updating such a library and the delegate call position has changed. Or when the delegate added new callbacks which don't get automatically implemented by increasing the version number in thebuild.gradle
.
CompositeAndroid let's you add delegates to your Activity without adding calls to the correct location. Such delegates are calledPlugins
. A Plugin is able to inject code at every position in the Activity lifecycle. It is able to override every method.
CompositeAndroid is available viajcenter
dependencies {// it's very important to use the same version as the support librarydef supportLibraryVersion="25.0.0"// contains CompositeActivity implementation"com.pascalwelsch.compositeandroid:activity:$supportLibraryVersion"// contains CompositeFragment and CompositeDialogFragment implementation"com.pascalwelsch.compositeandroid:fragment:$supportLibraryVersion"// core module (not required, only abstract classes and utils) implementation"com.pascalwelsch.compositeandroid:core:$supportLibraryVersion"}
Extend from one of the composite implementations when you want to add plugins. This is the only inheritance you have to make.
- public class MyActivity extends AppCompatActivity {+ public class MyActivity extends CompositeActivity {
- public class MyFragment extends Fragment { // v4 support library+ public class MyFragment extends CompositeFragment {
Use the constructor to add plugins. Do not add plugins in#onCreate()
. That's too late. ManyActivity
methods are called before#onCreate()
which could be important for a plugin to work.
publicclassMainActivityextendsCompositeActivity {finalLoadingIndicatorPluginloadingPlugin =newLoadingIndicatorPlugin();publicMainActivity() {addPlugin(newViewTrackingPlugin("Main"));addPlugin(loadingPlugin); }@OverridepublicvoidonCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState);// ...// example usage of the LoadingIndicatorPluginloadingPlugin.showLoadingIndicator(); }}
Read more about the ordering of the Pluginshere
This is the strength of CompositeAndroid. You don't really have to learn something new. It works like you'd extend youActivity
to add functionality. Let's change theTrackedActivity
from above and create aViewTrackingPlugin
.
Here the original
publicabstractclassTrackedActivityextendsAppCompatActivity {publicabstractStringgetTrackingName();@OverrideprotectedvoidonResume() {super.onResume();Analytics.trackView(getTrackingName()); }}
As plugin:
publicclassViewTrackingPluginextendsActivityPlugin {privatefinalStringmViewName;protectedTrackedPlugin(finalStringviewName) {mViewName =viewName; }@OverridepublicvoidonResume() {super.onResume();Analytics.trackView(mViewName); }}
The implementation inside ofonResume()
hasn't changed!
Here some information about plugins. The Activity example is used but it works the same for other classes, too.
- it's possible to overrideevery Activity method from a
Plugin
- execute code before calling
super
executes code beforesuper
of Activity - explicitly not calling
super
is allowed and results in not calling super of theActivity
. (The activity will tell if thesuper
call was required) - execute code after calling
super
executes code aftersuper
of Activity
Not everything works exactly like you'd use inheritance. Here is a small list of minor things you have to know:
- you can't call an
Activity
method of aPlugin
such asonResume()
orgetResources()
. Otherwise the call order of the added plugins is not guaranteed. Instead call those methods on the realActivity
withgetActivity.onResume()
orgetActivity.getResources()
.
CompositeActivity#onRetainCustomNonConfigurationInstance()
is final and required for internal usage, useCompositeActivity#onRetainCompositeCustomNonConfigurationInstance()
insteadCompositeActivity#getLastCustomNonConfigurationInstance()
is final and required for internal usage, useCompositeActivity#getLastCompositeCustomNonConfigurationInstance()
instead- Saving a NonConfigurationInstance inside of a
Plugin
works by overridingonRetainNonConfigurationInstance()
and returning an instance ofCompositeNonConfigurationInstance(key, object)
. Get the data again withgetLastNonConfigurationInstance(key)
and make sure you use the correctkey
.
CompositeAndroid
gets used in productions without major problems. There could be more performance related improvements but it works reliably right now.
Minor problems are:
- Support lib updatessometimes require and update of
CompositeAndroid
. I didn't expect this because the API should be really stable, but it happened in the past (upgrading from24.1.0
to24.2.0
). That's whyCompositeAndroid
has the same version name as the support library. Yes, the support library can be used with and olderCompositeAndroid
version. But it can break, as it happened already. Then again all upgrades from24.2.1
where 100% backwards compatible. We'll see what the future brings. - Generating a new release cannot be fully automated right now. It requires some steps in Android Studio to generate overrides and format the generated sources.
- Some methods are edge cases like
getLastNonConfigurationInstance()
andonRetainCustomNonConfigurationInstance()
did require manual written code.
It was a proof of conecpt and it turned out to work great. So great I haven't touched it a lot after the initial draft. Things like the documentation are still missing.I'm still keeping this project up to date but I don't invest much time in performance improvements. I don't need it, it works at it is for me.
- Navi of course, but
- it doesn't support all methods (only methods without return value)
- it does only support code execution before or after calling
super
, not very flexible - no plugin API
- Lightcycle
- supports only basic lifecycle methods.
- Decorator
- works only in scope of your own project. It doesn't allow including libraries providing plugins because the is no global Activity implementation.
- Every "DecoratedActivity" is generated for a specific usecase based on a blueprint you have to create every time
Copyright 2016 Pascal WelschLicensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
About
Composition over inheritance for Android components like Activity or Fragment
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.