gh-pages / wiki / Components

Components

object Components (source)

DevFun is designed to be modular, in terms of both its dependencies (limiting impact to main source tree) and its plugin-like architecture. Component Dependencies

Main Modules

Minimum required libraries - annotations and annotation processor.

DevFun demonstration

Annotations

Provides DevFun annotations and various interface definitions:

This library contains primarily interface definitions and inline functions, and will have a negligible impact on your method count and dex sizes. Apply to your main compile configuration:

implementation("com.nextfaze.devfun:devfun-annotations:2.1.0")

Compiler

Annotation processor DevFunProcessor that handles DeveloperFunction, DeveloperCategory, DeveloperReference, and DeveloperAnnotation annotations.

This should be applied to your non-main kapt configuration ‘kaptDebug’ to avoid running/using it on release builds.

kaptDebug("com.nextfaze.devfun:devfun-compiler:2.1.0")

Configuration options can be applied using Android DSL:

android {
     defaultConfig {
         javaCompileOptions {
             annotationProcessorOptions {
                 argument("devfun.argument", "value")
             }
         }
     }
}

Full list available at com.nextfaze.devfun.compiler.

Gradle Plugin

Used to configure/provide the compiler with the project/build configurations.

In your build.gradle add the DevFun Gradle plugin to your build script.

If you can use the Gradle plugins block (which you should be able to do - this locates and downloads it for you):

plugins {
    id("com.nextfaze.devfun") version "2.1.0"
}

Or the legacy method using apply; Add the plugin to your classpath (found in the jcenter() repository):

buildscript {
    dependencies {
        classpath("com.nextfaze.devfun:devfun-gradle-plugin:2.1.0")
    }
}

And in your build.gradle:

apply {
    plugin("com.nextfaze.devfun")
}

Core Modules

Modules that extend the accessibility of DevFun (e.g. add menu/http server).

Also see Experimental Modules below.

DevFun

Core of DevFun. Loads modules and definitions.

Apply to your non-main configuration:

debugImplementation("com.nextfaze.devfun:devfun:2.1.0")

Modules are loaded by DevFun using Java’s ServiceLoader.

DevFun loads, transforms, and sorts the generated function definitions, again via the ServiceLoader mechanism. To inject function invocations, InstanceProviders are used, which will attempt to locate (or create) object instances. A composite instance provider CompositeInstanceProvider at DevFun.instanceProviders is used via convenience function (extension) FunctionItem.call that uses the currently loaded devFun instance.

If using Dagger 2.x, you can use the devfun-inject-dagger2 module for a simple reflection based provider or related helper functions. A heavily reflective version will be used automatically, but if it fails (e.g. it expects a Component in your application class), a manual implementation can be provided. See the demo app DemoInstanceProvider for a sample implementation.

Menu demonstration

Adds a developer menu DevMenu, accessible by a floating cog CogOverlay (long-press to drag) or device button sequence KeySequence.

debugImplementation("com.nextfaze.devfun:menu:2.1.0")

Button sequences: (this are not configurable at the moment but are intended to be eventually)

internal val GRAVE_KEY_SEQUENCE = KeySequence.Definition(
    keyCodes = intArrayOf(KeyEvent.KEYCODE_GRAVE),
    description = R.string.df_menu_grave_sequence,
    consumeEvent = true
)
internal val VOLUME_KEY_SEQUENCE = KeySequence.Definition(
    keyCodes = intArrayOf(
        KeyEvent.KEYCODE_VOLUME_DOWN,
        KeyEvent.KEYCODE_VOLUME_DOWN,
        KeyEvent.KEYCODE_VOLUME_UP,
        KeyEvent.KEYCODE_VOLUME_DOWN
    ),
    description = R.string.df_menu_volume_sequence,
    consumeEvent = false
)

Menu controllers implement MenuController and can be added via devFun.module<DevMenu>() += MyMenuController().

Inject Modules

Modules to facilitate dependency injection for function invocation.

Dagger 2

Adds module InjectFromDagger2 which adds an InstanceProvider that can reflectively locate components or (if used) resolve Dagger2Component uses. Tested from Dagger 2.4 to 2.17.

debugImplementation("com.nextfaze.devfun:devfun-inject-dagger2:2.1.0")

Simply graphs should be well supported. More complex graphs should work (it has been working well in-house). Please report any issues you encounter.

The module also provides a variety of utility functions for manually providing your own instance provider using your components. See below for more details.

I’m always looking into better ways to support this, comments/suggestions are welcome.

Supported Versions

Dagger has been tested on the demo app from versions 2.4 to 2.17, and various in-house apps on more recent versions, and should function correctly for most simple scopes/graphs.

For reference the demo app uses three scopes; Singleton, Retained (fragments), and an Activity scope. It uses both type-annotated scoping and provides scoping. It keeps component instances in the activity and obtains the singleton scope via an extension function. In general this should cover most use cases - if you encounter any problems please create an issue.

Limitations

DevFun uses a number of methods iteratively to introspect the generated components/modules, however depending on scoping, visibility, and instantiation of a type it can be difficult to determine the source/scope in initial (but faster) introspection methods.

When all else fails DevFun will use a form of heavy reflection to introspect the generated code - types with a custom scope and no constructor arguments are not necessarily obtainable from Dagger (depends on the version) by any other means. To help with this ensure your scope is @Retention(RUNTIME) so that DevFun wont unintentionally create a new instance when it can’t find it right away.

Due to the way Dagger generates/injects it is not possible to obtain the instance of non-scoped types from the generated component/module as its instance is created/injected once (effectively inlined) at the inject site. It is intended to allow finding instances based on the context of the dev. function in the future (i.e. if the dev. function is in a fragment then check for the injected instance in the fragment etc.) - if this is desirable sooner make a comment in the issue #26.

Instance and Component Resolution

Unless you specify Dagger2Component annotations, DevFun will use a heavy-reflection based provider. Where possible DevFun will cache the locations of where it found various types - this is somewhat loose in that the provider cache still attempts to be aware of scoping.

Reflection Based

By default simply including the module will use the reflection-based component locator.

It will attempt to locate your component objects in your application class and/or your activity classes and use aforementioned utility functions.

If you place one or more Dagger2Component annotations (see below), then the reflective locator wont be used.

Annotation Based

For more control, or if the above method doesn’t (such as if you use top-level extension functions to retrieve your components, or you put them in weird places, or for whatever reason), then you can annotate the functions/getters with Dagger2Component. The scope/broadness/priority can be set on the annotation either via Dagger2Component.scope or Dagger2Component.priority. If unset then the scope will be assumed based on the context of its location (i.e. in Application class > probably the top level component, if static then first argument assumed to be the receiver, etc).

Note: For properties you can annotated to property itself (@Dagger2Component) or the getter explicitly (@get:Dagger2Component) if for some reason on the property doesn’t work (which could happen if it can’t find your getter - which is done via method name string manipulation due to KAPT limitations.

Example usage:

@Dagger2Component
val Context.applicationComponent: ApplicationComponent?
    get() = (applicationContext as DaggerApplication).applicationComponent
@get:Dagger2Component // if we want to specify getter explicitly
lateinit var activityComponent: ActivityComponent
    private set
@get:Dagger2Component(Dagger2Scope.RETAINED_FRAGMENT)
lateinit var retainedComponent: RetainedComponent
    private set
Custom Instance Provider

Since the reflection locator and annotation based still make assumptions and are bit inefficient because of it, sometimes you may need to implement your own instance provider.

See demo for example implementation: DemoInstanceProvider

Util Modules

Modules with frequently used or just handy functions (e.g. show Glide memory use).

Developers love reusable utility functions, we all have them, and we usually copy-paste them into new projects. Adding them to modules and leveraging dependency injection allows for non-static, easily invokable code reuse.

Still playing with this concept and naming conventions etc.

Glide

Module GlideUtils provides some utility functions when using Glide.

debugImplementation("com.nextfaze.devfun:devfun-util-glide:2.1.0")

Features:

Leak Canary

Module LeakCanaryUtils provides some utility functions when using Leak Canary.

debugImplementation("com.nextfaze.devfun:devfun-util-leakcanary:2.1.0")

Features:

Invocation UI with custom color picker view

Invoke Modules

Modules to facilitate function invocation.

Color Picker View

Adds a parameter annotation ColorPicker that lets the invocation UI render a color picker view for the associated argument.

Note: Only needed if you don’t include devfun-menu (as it uses/includes the color picker transitively).

debugImplementation("com.nextfaze.devfun-invoke-view-colorpicker:2.1.0")

Experimental Modules

These modules are mostly for use experimenting with various use-cases.

They generally work, but are likely to be buggy and have various limitations and nuances. Having said that, it would be nice to expand upon them and make them nicer/more feature reach in the future.

Some future possibilities:

HttpD

Module DevHttpD adds a local HTTP server (uses NanoHttpD).

Provides a single POST method invoke with one parameter hashCode (expecting FunctionItem.hashCode)

debugImplementation("com.nextfaze.devfun:httpd:2.1.0")

Use with HttpD Front-end. Current port and access instructions are logged on start.

Default port is 23075. If this is in use another is deterministically generated from your package (this might become the default).

If using AVD, forward port:

adb forward tcp:23075 tcp:23075

Then access via IP: http://127.0.0.1:23075/

Custom Port

Can be set via resources:

<integer name="df_httpd_default_port">12345</integer>

Or before initialization devDefaultPort (i.e. if not using auto-init content provider):

devDefaultPort = 12345 // top level value located in com.nextfaze.devfun.httpd

Http Front-end

Module HttpFrontEnd generates an admin interface using SB Admin 2 (similar to DevMenu), allowing function invocation from a browser.

Depends on DevHttpD.

debugImplementation("com.nextfaze.devfun:httpd-frontend:2.1.0")

Page is rather simple at the moment, but in the future it’s somewhat intended (as a learning exercise) to create a React front end using Kotlin or something.

HTTP Server

Stetho

Module DevStetho allows generated methods to be invoked from Chrome’s Dev Tools JavaScript console.

debugImplementation("com.nextfaze.devfun:devfun-stetho:2.1.0")

Opening console will show available functions. e.g. Context_Enable_Account_Creation()

Extremely experimental and limited functionality.

Stetho Integration