uber / uber/motif

Assisted Injection Proposal

Open
#175 18 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
537
Forks
46
PR merge metrics
No merged PRs in 30d

Description

This proposal is to have some sort of AssistedInjection support for Motif.

Motif already has an API for factory implementation to create child Scope with given dynamic variables, but it is restricted only to Scope creation. By extending this functionality to any other type, we can already achieve assisted instance creation via Motif.

Problem

Currently, it is not supported to provide an instance with dynamic variables to child scope. In order to provide that, you can create a separate instance that would be responsible for generating the instance you need and provide that object as static instance.

Example; Assuming a callback needs to hold on to an instance so that can notify back with given instance, that means a new instance is required per given item.

class SampleImplementation(
    private val dynamicData: DynamicData,
    private val logger: Logger
) : SampleInterface { }

@motif.Scope
interface SampleScope {
    fun createChildScope(callback: SampleInterface): ChildScope
}

class ConsumerExample(
    private val sampleScope: SampleScope,
    private val logger: Logger
) {

    fun usage(dynamicData: DynamicData) {
        val childScope = sampleScope.createChildScope(
            new SampleImplementation(dynamicData, logger)
        )
    }

}

This makes ConsumerExample to know about how to create an instance of SampleInterface, which violates inversion of control and also creates a direct dependency to Logger even though it is not needed besides creating the instance for the callback.

Workaround

In order to work around this, we can create a factory implementation that can create an instance of the callback lazily and provide this factory in DI so that ConsumerExample can have the factory and generate the instance it needs.

interface SampleInterfaceFactory {
    fun create(dynamicData: DynamicData): SampleInterface
}


@motif.Scope
interface SampleScope {

    @motif.Objects
    open class Object {

        fun sampleInterfaceFactory(logger: Logger): SampleInterfaceFactory {
            return dynamicData -> SampleImplementation(dynamicData, logger)
        }

    }

}

class ConsumerExample(
    private val sampleScope: SampleScope,
    private val sampleInterfaceFactory: SampleInterfaceFactory
) {

    fun usage(dynamicData: DynamicData) {
        val childScope = sampleScope.createChildScope(
            sampleInterfaceFactory.create(dynamicData)
        )
    }

}

Even though this is doable, it still introduces a lot of boilerplate code.

Proposed Solution

Considering we already have enough information in DI tree, we can benefit from Assisted Injection and pass only the dynamic dependencies and have the rest provided by Motif.

@motif.Scope
interface SampleScope {

    fun createChildScope(callback: SampleInterface): ChildScope

    fun createSampleImplementation(dynamicData: DynamicData): SampleImplementation

}


class ConsumerExample(private val sampleScope: SampleScope) {

    fun usage(dynamicData: DynamicData) {
        val childScope = sampleScope.createChildScope(
            sampleScope.createSampleImplementation(dynamicData)
        )
    }

}

Motif should be able to create an instance of SampleImplementation with give DynamicData given that it already has a Logger instance in the DI tree. So the implementation would need to diff given dynamic dependencies with constructor dependencies and satisfy remaining dependencies from DI.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Motif's existing Scope API, especially createChildScope and its factory implementation support, then trace how constructor dependencies are resolved. The proposal is complete when a scope can create an instance from supplied dynamic dependencies while resolving remaining dependencies from the DI tree, with coverage for the assisted-injection behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
mobile-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.