Git Product home page Git Product logo

compositeandroid's Introduction

CompositeAndroid

License

Composition over inheritance

Allows to add functionality into an Android Activity. Just because we all have a BaseActivity in our projects containing too much unused stuff. When it grows, it get unmaintainable.

Possible Usecases

  • 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 like Mosby (without extending from a MvpActivity) or Flow to your Activities when you need it
  • and so much more...

State of the Art

Given you have an Activity showing a list of tweets (TweetStreamActivity) and you want add view tracking.

Inheritance

You could do it with inheritance and use TrackedTweetStreamActivity from now on:

public class TrackedTweetStreamActivity extends TweetStreamActivity {

    @Override
    protected void onResume() {
        super.onResume();
        Analytics.trackView("stream");
    }
}

more likely you would create a TrackedActivity and extend the TweetStreamActivity from it:

public abstract class TrackedActivity extends AppCompatActivity {

    public abstract String getTrackingName();

    @Override
    protected void onResume() {
        super.onResume();
        Analytics.trackView(getTrackingName());
    }
}
public class TrackedTweetStreamActivity extends TrackedActivity {

    @Override
    public String getTrackingName() {
        return "stream";
    }
}

Both solutions work but don't scale well. You'll most likely end up with big inheritance structures:

class MvpActivity extends AppCompatActivity { ... }

class BaseActivity extends AppCompatActivity { ... }

class BaseMvpActivity extends MvpActivity { ... }

class WizardUiActivity extends BaseActivity { ... }

class TrackedWizardUiActivity extends WizardUiActivity { ... }

class TrackedBaseActivity extends BaseActivity { ... }

class TrackedMvpBaseActivity extends BaseMvpActivity { ... }

Delegation

Some libraries out there provide both, a specialized Activity extending AppCompatActivity and a delegate with a documentation when to call which function of the delegate in your Activity.

public class TrackingDelegate {

    /**
     * usage:
     * <pre>{@code
     *
     * @Override
     * protected void onResume() {
     *     super.onResume();
     *     mTrackingDelegate.onResume();
     * }
     * } </pre>
     */
    public void onResume() {
        Analytics.trackView("<viewName>");
    }

}
public class TweetStreamActivity extends AppCompatActivity {

    private final TrackingDelegate mTrackingDelegate = new TrackingDelegate();

    @Override
    protected void onResume() {
        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 the build.gradle.

Composite Android solution

CompositeAndroid let's you add delegates to your Activity without adding calls to the correct location. Such delegates are called Plugins. A Plugin is able to inject code at every position in the Activity lifecycle. It is able to override every method.

Get Started Download

CompositeAndroid is available via jcenter

dependencies {
    // it's very important to use the same version as the support library
    def 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 a composite implementation

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 {

Add a plugins

Use the constructor to add plugins. Do not add plugins in #onCreate(). That's too late. Many Activity methods are called before #onCreate() which could be important for a plugin to work.

public class MainActivity extends CompositeActivity {

    final LoadingIndicatorPlugin loadingPlugin = new LoadingIndicatorPlugin();

    public MainActivity() {
        addPlugin(new ViewTrackingPlugin("Main"));
        addPlugin(loadingPlugin);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...

        // example usage of the LoadingIndicatorPlugin
        loadingPlugin.showLoadingIndicator();
    }
}

Read more about the ordering of the Plugins here

Write a plugin

This is the strength of CompositeAndroid. You don't really have to learn something new. It works like you'd extend you Activity to add functionality. Let's change the TrackedActivity from above and create a ViewTrackingPlugin.

Here the original

public abstract class TrackedActivity extends AppCompatActivity {

    public abstract String getTrackingName();

    @Override
    protected void onResume() {
        super.onResume();
        Analytics.trackView(getTrackingName());
    }
}

As plugin:

public class ViewTrackingPlugin extends ActivityPlugin {

    private final String mViewName;

    protected TrackedPlugin(final String viewName) {
        mViewName = viewName;
    }

    @Override
    public void onResume() {
        super.onResume();
        Analytics.trackView(mViewName);
    }
}

The implementation inside of onResume() hasn't changed!

Plugin features

Here some information about plugins. The Activity example is used but it works the same for other classes, too.

  • it's possible to override every Activity method from a Plugin
  • execute code before calling super executes code before super of Activity
  • explicitly not calling super is allowed and results in not calling super of the Activity. (The activity will tell if the super call was required)
  • execute code after calling super executes code after super of Activity

restrictions

Not everything works exactly like you'd use inheritance. Here is a small list of minor things you have to know:

Important for all Plugin authors

  • you can't call an Activity method of a Plugin such as onResume() or getResources(). Otherwise the call order of the added plugins is not guaranteed. Instead call those methods on the real Activity with getActivity.onResume() or getActivity.getResources().

onRetainNonConfigurationInstace

  • CompositeActivity#onRetainCustomNonConfigurationInstance() is final and required for internal usage, use CompositeActivity#onRetainCompositeCustomNonConfigurationInstance() instead
  • CompositeActivity#getLastCustomNonConfigurationInstance() is final and required for internal usage, use CompositeActivity#getLastCompositeCustomNonConfigurationInstance() instead
  • Saving a NonConfigurationInstance inside of a Plugin works by overriding onRetainNonConfigurationInstance() and returning an instance of CompositeNonConfigurationInstance(key, object). Get the data again with getLastNonConfigurationInstance(key) and make sure you use the correct key.

Project stage

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 updates sometimes require and update of CompositeAndroid. I didn't expect this because the API should be really stable, but it happened in the past (upgrading from 24.1.0 to 24.2.0). That's why CompositeAndroid has the same version name as the support library. Yes, the support library can be used with and older CompositeAndroid version. But it can break, as it happened already. Then again all upgrades from 24.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() and onRetainCustomNonConfigurationInstance() 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.

Inspiration and other Android Composition Libraries

  • 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

License

Copyright 2016 Pascal Welsch

Licensed 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.0

Unless required by applicable law or agreed to in writing, software
distributed 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 and
limitations under the License.

compositeandroid's People

Contributors

janstoltman avatar passsy avatar patrickstoklasa avatar xiandalong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

compositeandroid's Issues

Support library version 25.0.0

I would like use support library version 25.0.0 with CompositeAndroid but I got error:

Error:Could not find com.pascalwelsch.compositeandroid:activity:25.0.0.

Can you update CompositeAndroid to 25.0.0 ?

Use Dagger2 injected objects in Plugin

Hi,

I really like this library and it has brought some great improvements into my code. Thanks :-)
However I'm facing a challenge now:

I'm using Dagger2 as a dependency injection framework in my app.
I have a (CompositeAndroid) plugin that needs to have access to some injected classes.
However since plugins are added in the constructor of an Activity (or Fragment) and fields are injected in onCreate() of the root base Activity it is not possible to access injected fields on construction so I cannot pass it as a parameter to the plugin from the Activity.

Example:

abstract class LockableSupportActivityBase : DaggerSupportActivityBase() {

    @Inject
    protected lateinit var preferenceHandler: PreferenceHandler

    init {
        // this throws NotInitializedException on "preferenceHandler"
        addActivityPlugins(LockPlugin(preferenceHandler))
    }

}

Injecting the Plugin itself is also not possible since it has to be added to the Activity in the constructor and the activity also has access to injected properties only right after super.onCreate() (so it's too late).

Example:

abstract class LockableSupportActivityBase : DaggerSupportActivityBase() {

    @Inject
    protected lateinit var lockPlugin: LockPlugin

    init {
        // this throws NotInitializedException on "lockPlugin"
        addActivityPlugins(lockPlugin)
    }

}

Is there a way to work around this or is this a problem that needs a more sophisticated fix?

Memory usage

Hi I don't know why but it seams that the library adds a huge amount of memory usage. As soon as I changed to the CompositeActivity the memory usage increased by 80mb.
I'm not very experienced with the memory analytics tool of android studio but I try to figure out what's the problem. If you're interested I can share you more details private.

Best
Alex

onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager

Hi,

I stumble across an error when getting the layoutInfalter from a CompositeFragment. Here is the stacktrace:

Fatal Exception: java.lang.IllegalStateException: onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager.
       at android.support.v4.app.Fragment.getLayoutInflater(Fragment.java:1151)
       at com.pascalwelsch.compositeandroid.fragment.CompositeFragment.super_getLayoutInflater(CompositeFragment.java:1310)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$7.call(FragmentDelegate.java:204)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$7.call(FragmentDelegate.java:197)
       at com.pascalwelsch.compositeandroid.fragment.FragmentPlugin.getLayoutInflater(FragmentPlugin.java:149)
       at com.pascalwelsch.compositeandroid.fragment.FragmentPlugin.getLayoutInflater(FragmentPlugin.java:1269)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$7.call(FragmentDelegate.java:202)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$7.call(FragmentDelegate.java:197)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate.getLayoutInflater(FragmentDelegate.java:208)
       at com.pascalwelsch.compositeandroid.fragment.CompositeFragment.getLayoutInflater(CompositeFragment.java:163)
       at android.support.v4.app.Fragment.onGetLayoutInflater(Fragment.java:1101)
       at com.pascalwelsch.compositeandroid.fragment.CompositeFragment.super_onGetLayoutInflater(CompositeFragment.java:1710)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$32.call(FragmentDelegate.java:769)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$32.call(FragmentDelegate.java:762)
       at com.pascalwelsch.compositeandroid.fragment.FragmentPlugin.onGetLayoutInflater(FragmentPlugin.java:528)
       at com.pascalwelsch.compositeandroid.fragment.FragmentPlugin.onGetLayoutInflater(FragmentPlugin.java:1456)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$32.call(FragmentDelegate.java:767)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate$32.call(FragmentDelegate.java:762)
       at com.pascalwelsch.compositeandroid.fragment.FragmentDelegate.onGetLayoutInflater(FragmentDelegate.java:773)
       at com.pascalwelsch.compositeandroid.fragment.CompositeFragment.onGetLayoutInflater(CompositeFragment.java:538)
       at android.support.v4.app.Fragment.performGetLayoutInflater(Fragment.java:1132)
       at android.support.v4.app.Fragment.getLayoutInflater(Fragment.java:1117)
       at com.my.app.features.event.EventDetailFragment.attachHeader(EventDetailFragment.java:66)
       at com.my.app.features.event.EventDetailFragment.lambda$beforeInflatingForms$0$EventDetailFragment(EventDetailFragment.java:91)
       at com.my.app.features.event.EventDetailFragment$$Lambda$0.doInUIThread(Unknown Source)
       at com.arasthel.asyncjob.AsyncJob$1.run(AsyncJob.java:46)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6776)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)

I dig a bit to find out how this was happening and I found out that the CompositeFragment is using a deprecated method of the Fragment class. It is using getLayoutInflater(Bundle savedFragmentState) instead of getLayoutInflater(). Maybe this won't solve the issue but I think this it still a point to figure out.

Quoting the javadoc of this function:

* @deprecated Override {@link #onGetLayoutInflater(Bundle)} or call
* {@link #getLayoutInflater()} instead of this method.

Hope this help.

AndroidX support ?

Hello, may we rely on support for androidX ? Or this project is closed and not supported anymore?

Migration from CompositeAndroid to official Android support for composition - question

Hi, as of AndroidX supports lifecycle and Activity result listening (currently rc 'androidx.activity:activity:1.2.0-rc01') it seemed that this library could be replaced. But so far difference is that lifecycle listeners can be add only before onResume() what requires quite some changes to old code bases, when you want for example catch onActivityResult() from dialog (eg. open android file chooser). And dialog is open from menu, which is created by onCreateOptionsMenu().

Am I missing something or is there possibility do add lifecycle listener after onResume?

ComponentActivity
ActivityResultCaller#registerForActivityResult

Implementation seems very heavy

The implementation seems very heavy when I look at the source code. I stopped being as worried about garbage collection with ART, but it seems like each stage of the Activity or Fragment lifecycle is creating at least 1, if not more objects:

return callFunction("getContext()", new PluginCall<FragmentPlugin, Context>() {
            @Override
            public Context call(final NamedSuperCall<Context> superCall,
                    final FragmentPlugin plugin, final Object... args) {

                return plugin.getContext(superCall);

            }
        }, new SuperCall<Context>() {
            @Override
            public Context call(final Object... args) {
                return getFragment().getContext__super();
            }
        });

I have no measured data on this, but couldn't this be easily implemented without the overhead of multiple function objects created at each method call?

getFragment() is null

Not sure I really understood how this is working. I want to use some methods that needs a context inside my FragmentPlugin (such as getString(), etc.). I tought I would simply use getFragment().getString() or getCompositeDelegate().getString() but both getFragment() and getCompositeDelegate() returns null.

How should I refer to the hosting fragment? Should I add it through my default constructor?

Thank's !

Add Lint warning for later API methods

Some methods have been introduced later. Right now lint doesn't warn for such calls. It warns for AppCompatActivity but not for CompositeActivity. I.e. Activity#isDestroyedI()

The @RequiresApi annotation could be added for such methods. It could be generated by the platform-tools/api/api-versions.xml which is used for lint warnings.

Mosby

Hi,

in the documentation you wrote about integrating the mosby library as a plugin. Have you done so already? I started doing it but having some trouble to get it done.
Furthermore I'd like to start a repo with common plugins - If you're interested, I would contribute them directly here.

Best
Alex

Incompatibility with EventBus on Android 6: NoClassDefFoundError

Hello,
this is not a bug report, just would like to point it out for other people having same issue.
I'm using this library with GreenRobot's EventBus. On some devices running Android 6 you could get NoClassDefFoundError telling that android.view.DragAndDropPermissions does not exists. This is correct because DragAndDropPermissions has been added on API level 24 and happens because of what explained here under "A java.lang.NoClassDefFoundError is throw when a subscriber class is registered. What can I do?".

Only solution I've found after following every suggestion on EventBus' FAQ has been to modify CompositeAndroid removing from library's classes the override of methods that returns a DragAndDropPermissions. Obviously I know that this could break your library, but I'm not using that methods in my activities.
If anyone could suggest a better method it will be appreciated.

If anyone has the same problem: I've forked this repo and published on jitpack.io a release with the mentioned workaround: https://jitpack.io/#francescopedronomnys/CompositeAndroid/25.3.1-1000

Bye, Francesco

Mosby usage: missing `onRetainCustomNonConfigurationInstance` method in ActivityPlugin

Hi,

Encouraged by your usecase reccomendation:

  • gradually add libraries like Mosby (without extending from a MvpActivity) or Flow to your Activities when you need it

I have started extendingActivityPlugin in way that will allow me to have Mosby presenter attached to it.

I have noticed that in ActivityPlugin there is onRetainNonConfigurationInstance method, although the documentation states clearly:

Retain all appropriate fragment and loader state. You can NOT
override this yourself! Use {@link #onRetainCustomNonConfigurationInstance()}
if you want to retain your own state.

and moreover the onRetainCustomNonConfigurationInstance method is missing there.

I wanted to override the onRetainCustomNonConfigurationInstance method in way it is done in Mosby MvpActivity to support Presenter lifecycle, but as I said, there is no such method in ActivityPlugin. Have you tried using Mosby with CompositeAndroid? Shall I do it in some other way?

Update to Oreo ApiLevel 26 ambiguous findViewById(int)

After I updated my compileSdk and targetSdk to 26 I got the following error in several classes:

error: reference to findViewById is ambiguous
both method findViewById(int) in AppCompatActivity and method findViewById(int) in CompositeActivity match
where T is a type-variable:
T extends View declared in method findViewById(int)

Any idea how to fix it?

Thanks & Regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.