Skip to content

anioutkazharkova/kmm-di-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

KMM DI Sample

Sample demo app with integrated Multiplatform-DI

Mobile client for Movies DB open API with common BL and architecture.

Features

  • Common architecture (VIP)
  • Common network (Ktor)
  • Common dependency injection
  • Coroutines for concurrency

Next

  • Support of Kotlin 1.5.0
  • Suport Coroutine Flows
  • Support ViewBinding in Android
  • Support of Jetpack Compose and SwiftUI

Architecture and common concurrency

App supports VIP architecture (sort of Clean architecture):

  • Interactor to communicate with common BL (IInteractor + BaseInteractor)
  • Presenter to process data from interactor and send to bound view (IPresenter + BasePresenter)
  • View (hold a link to interactor) (IView)

Also every VIP-module contains specific configurator to create all components and connect them together.

E.g:

class MoviesListConfigurator : IConfigurator {
    companion object {
        val instance = MoviesListConfigurator()
    }

    override fun create(view: IView): IInteractor? {
        val interactor: IMoviesListInteractor = MoviesListInteractor()
        val presenter = MoviesListPresenter()
        interactor.presenter = presenter
        presenter.view = view as? IMoviesListView
        return interactor
    }
}

Every interactor implements interface:

interface IInteractor {
    fun setup(di: DIManager)

    fun attachView()

    fun detachView()
}

Also it derives BaseInteractor with incapsulated common concurrency:

abstract class BaseInteractor<T : IView>(private val coroutineContext: CoroutineContext) {
    protected lateinit var scope: ModuleCoroutineScope

    fun attachView() {
        scope = ModuleCoroutineScope(coroutineContext)
    }

    fun detachView() {
        scope.viewDetached()
    }
}

class ModuleCoroutineScope(
    context: CoroutineContext
) : CoroutineScope {

    private var onViewDetachJob = Job()
    override val coroutineContext: CoroutineContext = context + onViewDetachJob

    fun viewDetached() {
        onViewDetachJob.cancel()
    }
}

All dependencies should be resolved in setup method of interactor:

class MoviesListInteractor :
    BaseInteractor<IMoviesListView>(uiDispatcher),
    IMoviesListInteractor {
    private var moviesService: MoviesService? = null
    override var presenter: IMoviesListPresenter? = null

    private var moviesList: ArrayList<MoviesItem> = arrayListOf()

    override fun setup(di: DIManager) {
        this.moviesService = di.resolve<MoviesService>(MoviesService::class) as? MoviesService
    }

The connection between view and interactor should be setup on native side:

// iOS MoviesListVC.swift

 @InjectedInView  var interactor: IMoviesListInteractor?

 override func viewDidLoad() {
        super.viewDidLoad()
        
        $interactor.view = self
   }
//Android MoviesListFragment.kt
val interactor: IMoviesListInteractor? by interactors(this)

//or 

var interactor: IMoviesListInteractor? = null

 override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.movies_list_fragment, container, false)?.also {
            this.interactor = App.container.resolve(this) as IMoviesListInteractor?
        }
    }

More info about property wrappers and delegates with DI you can find in WIKI (soon)