Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird behavior when splitting dependencies into different modules #2129

Open
MJavoso opened this issue Feb 11, 2025 · 0 comments
Open

Weird behavior when splitting dependencies into different modules #2129

MJavoso opened this issue Feb 11, 2025 · 0 comments

Comments

@MJavoso
Copy link

MJavoso commented Feb 11, 2025

I've encountered a weird behavior when separating my dependencies into different modules.
First, I had all my dependencies into one module:

val module = module {
    //Initializables
    single(named(NamedDependencies.SqliteDb)) {
        DatabaseManager.Companion
    }.bind<Initializable>()
    single(named(NamedDependencies.WindowsKeyboard)) {
        WindowsKeyboardListenerService.Companion
    }.bind<Initializable>()

    //Helpers
    factoryOf(::DefaultDatabaseObserver).bind<DatabaseObserver>()
    singleOf(::ToDoDeletionSchedulerService).bind()
    factoryOf(::ToDoInfoSorter).bind()
    factory { { InetSocketAddress(ConnectionService.DEFAULT_HOST, ConfigurationManager.port) } }
    singleOf(::KtorConnectionService).bind<ConnectionService>()
    single {
        HttpClient(OkHttp) {
            install(Logging)
            install(HttpTimeout)
            install(ContentNegotiation) {
                json()
            }
        }
    }
    singleOf(::KtorServerService).bind()
    single { p ->
        WindowsKeyboardListenerService(p[0], p[1])
    }.bind<KeyboardListenerService>()

    // Databases
    singleOf(::DatabaseManager).bind<DatabaseManager>()

    //DAOs
    single { ApplicationDao.Companion }.bind<ApplicationDaoClass>()
    single { ExecutableDao.Companion }.bind<ExecutableDaoClass>()
    single { ToDoDao.Companion }.bind<ToDoDaoClass>()
    single { ScheduledToDoDeletionDao.Companion }.bind<ScheduledToDoDeletionDaoClass>()

    // Repositories
    factoryOf(::SqliteApplicationsRepositoryImpl).bind<ApplicationsRepository>()
    factoryOf(::SqliteExecutablesRepositoryImpl).bind<ExecutablesRepository>()
    factoryOf(::SqliteToDosRepositoryImpl).bind<ToDosRepository>()

    //Services
    factoryOf(::ApplicationsService).bind<IApplicationsService>()
    factoryOf(::ApplicationsGrouperService).bind()
    factoryOf(::ToDosService).bind<IToDosService>()
    factoryOf(::UpdateCheckerService)
    single { ConfigurationService }.bind()

    // ViewModels
    factoryOf(::ConfigurationViewModel).bind()
    factory { p -> EmptyErrorViewModel(get(), get(), p.getOrNull()) }
    factory { p -> ToDosViewModel(get(), get(), p.getOrNull(), p.getOrNull()) }
    factory { p -> ToDoEditionViewModel(get(), p.getOrNull(), p.getOrNull()) }
    factoryOf(::ApplicationsViewModel).bind()
}

I refactored the code, so all relationed dependencies would go in a module, separated from other modules, and combined everything into one mainModule like this:

val initializableComponentsModule = module {
    single<Initializable>(named(NamedDependencies.SqliteDb)) {
        DatabaseManager.Companion
    }
    single<Initializable>(named(NamedDependencies.WindowsKeyboard)) {
        WindowsKeyboardListenerService.Companion
    }
}

val helpersModule = module {
    factoryOf(::DefaultDatabaseObserver).bind<DatabaseObserver>()
    singleOf(::ToDoDeletionSchedulerService).bind()
    factoryOf(::ToDoInfoSorter).bind()
    factory<() -> InetSocketAddress> { { InetSocketAddress(ConnectionService.DEFAULT_HOST, ConfigurationManager.port) } }
    singleOf(::KtorConnectionService).bind<ConnectionService>()
    single {
        HttpClient(OkHttp) {
            install(Logging)
            install(HttpTimeout)
            install(ContentNegotiation) {
                json()
            }
        }
    }
    singleOf(::KtorServerService).bind()
    single { p ->
        WindowsKeyboardListenerService(p[0], p[1])
    }.bind<KeyboardListenerService>()
}

val databaseModule = module {
    singleOf(::DatabaseManager).bind<DatabaseManager>()
}

val daosModule = module {
    single<ApplicationDaoClass> { ApplicationDao.Companion }
    single<ExecutableDaoClass> { ExecutableDao.Companion }
    single<ToDoDaoClass> { ToDoDao.Companion }
    single<ScheduledToDoDeletionDaoClass> { ScheduledToDoDeletionDao.Companion }
}

val repositoriesModule = module {
    factoryOf(::SqliteApplicationsRepositoryImpl).bind<ApplicationsRepository>()
    factoryOf(::SqliteExecutablesRepositoryImpl).bind<ExecutablesRepository>()
    factoryOf(::SqliteToDosRepositoryImpl).bind<ToDosRepository>()
}

val servicesModule = module {
    factoryOf(::ApplicationsService).bind<IApplicationsService>()
    factoryOf(::ApplicationsGrouperService).bind()
    factoryOf(::ToDosService).bind<IToDosService>()
    factoryOf(::UpdateCheckerService)
    single { ConfigurationService }.bind()
}

val viewModelsModule = module {
    factoryOf(::ConfigurationViewModel).bind()
    factory { p -> EmptyErrorViewModel(get(), get(), p.getOrNull()) }
    factory { p -> ToDosViewModel(get(), get(), p.getOrNull(), p.getOrNull()) }
    factory { p -> ToDoEditionViewModel(get(), p.getOrNull(), p.getOrNull()) }
    factoryOf(::ApplicationsViewModel).bind()
}

val mainModule = module {
    includes(
        initializableComponentsModule,
        helpersModule,
        databaseModule,
        daosModule,
        repositoriesModule,
        servicesModule,
        viewModelsModule
    )
}

In the first version, when I invoke this code, it worked fine:

viewModel(
        koinInject<ToDoEditionViewModel>(parameters = {
            parametersOf(
                applicationId, todoId // Both of these parameters can be null Longs
            )
        })
    )

Now I'll explain the behavior of both previous and current version:
Before:

  1. Pass applicationId and todoId (applicationId = any number, 1 for this instance, todoId = null)
  2. factory method receives 1 and null. And calling p.getOrNull() passes arguments in order
  3. ToDoEditionViewModel gets 1 and null

Now (after refactor):

  1. Pass applicationId and todoId (applicationId = 1, todoId = null)
  2. factory method receives 1 and null (logged p.values list). Calling p.getOrNull() gets 1 and 1. Second value (null), is ignored or lost
  3. ToDoEditionViewModel gets 1 and 1

The project was started in compose desktop at a version where ViewModel class didn't exist yet in CMP. That's why I don't use viewModel factory methods of Koin and use a custom viewModel function at compose code, where dependency is injected.

Koin version:
koin-core: 4.0.0
koin-compose: 4.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant