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.
Main Modules
Minimum required libraries - annotations and annotation processor.
Annotations
Provides DevFun annotations and various interface definitions:
- DeveloperFunction
- DeveloperCategory
- DeveloperReference
- DeveloperAnnotation
- DeveloperLogger
- DeveloperProperty
- Dagger2Component
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
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.
- Currently kapt doesn’t support multi-staged processing of generated Kotlin code.
- Possibly consider generating Java
Component
interfaces for some types? - Likely will investigate the new SPI functionality in Dagger 2.17+ once it becomes more stable.
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:
- Where a top-level/singleton/application component is retrieved via an extension function (from the demo):
@Dagger2Component
val Context.applicationComponent: ApplicationComponent?
get() = (applicationContext as DaggerApplication).applicationComponent
- Where a component is kept in the activity (from the demo):
@get:Dagger2Component // if we want to specify getter explicitly
lateinit var activityComponent: ActivityComponent
private set
- Where a differently scoped component is also kept in the activity, we can set the scope manually (Dagger2Scope) (from the demo):
@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.
- Disable the automatic locator: set useAutomaticDagger2Injector to
false
(can be done at any time). - Add your own provider using
devFun += MyProvider
(see InstanceProvider for more details). - Utility function tryGetInstanceFromComponent to help (though again it relies heavily on reflection and don’t consider scoping very well).
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:
- Clear memory cache
- Clear disk cache
- Log current memory/disk cache usage
Leak Canary
Module LeakCanaryUtils provides some utility functions when using Leak Canary.
debugImplementation("com.nextfaze.devfun:devfun-util-leakcanary:2.1.0")
Features:
- Launch
DisplayLeakActivity
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: Use ktor instead of Nano
- HttpD Simple Index: Provide a themed react page or something pretty/nice
- Add a Kotlin REPL module
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.
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.