diff --git a/dependencies.gradle b/dependencies.gradle
index ce6e4309..2e38bee1 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -20,6 +20,7 @@ ext.versions = [
         okhttp               : "3.10.0",
         semver               : "1.0.0",
         twitterSerial        : "0.1.6",
+        koin                 : "0.9.3",
 
         junit                : "4.12",
         mockito              : "2.16.0",
@@ -51,6 +52,8 @@ ext.other = [
         okhttpLogging      : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp",
         semver             : "net.swiftzer.semver:semver:$versions.semver",
         twitterSerial      : "com.twitter.serial:serial:$versions.twitterSerial",
+        koinCore           : "org.koin:koin-core:$versions.koin",
+        koinAndroid        : "org.koin:koin-android:$versions.koin"
 ]
 
 ext.testing = [
diff --git a/subsonic-api/build.gradle b/subsonic-api/build.gradle
index 4d6a0c05..78f37860 100644
--- a/subsonic-api/build.gradle
+++ b/subsonic-api/build.gradle
@@ -13,6 +13,8 @@ dependencies {
     api other.kotlinStdlib
     api other.retrofit
     api other.jacksonConverter
+    api other.koinCore
+
     implementation(other.jacksonKotlin) {
         exclude module: 'kotlin-reflect'
     }
@@ -36,7 +38,8 @@ jacoco {
 ext {
     // Excluding data classes
     jacocoExclude = [
-            '**/models/**'
+            '**/models/**',
+            '**/di/**'
     ]
 }
 
diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt
index 063a5cf7..2b828a1a 100644
--- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt
+++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt
@@ -21,8 +21,14 @@ class GetStreamUrlTest {
 
     @Before
     fun setUp() {
-        client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
-                USERNAME, PASSWORD, V1_6_0, CLIENT_ID)
+        val config = SubsonicClientConfiguration(
+            mockWebServerRule.mockWebServer.url("/").toString(),
+            USERNAME,
+            PASSWORD,
+            V1_6_0,
+            CLIENT_ID
+        )
+        client = SubsonicAPIClient(config)
         val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString()
         expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" +
                 "&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt
index 6d4dfffd..d82c34c2 100644
--- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt
+++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClientTest.kt
@@ -10,11 +10,18 @@ import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
 abstract class SubsonicAPIClientTest {
     @JvmField @Rule val mockWebServerRule = MockWebServerRule()
 
+    protected lateinit var config: SubsonicClientConfiguration
     protected lateinit var client: SubsonicAPIClient
 
     @Before
     fun setUp() {
-        client = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
-                USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID)
+        config = SubsonicClientConfiguration(
+            mockWebServerRule.mockWebServer.url("/").toString(),
+            USERNAME,
+            PASSWORD,
+            CLIENT_VERSION,
+            CLIENT_ID
+        )
+        client = SubsonicAPIClient(config)
     }
 }
diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt
index 1a10e6cb..536cb8a7 100644
--- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt
+++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiPasswordTest.kt
@@ -10,8 +10,9 @@ import org.junit.Test
 class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
     @Test
     fun `Should pass PasswordMD5Interceptor in query params for api version 1 13 0`() {
-        val clientV12 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
-                USERNAME, PASSWORD, SubsonicAPIVersions.V1_14_0, CLIENT_ID)
+        val clientV12 = SubsonicAPIClient(
+            config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_14_0)
+        )
         mockWebServerRule.enqueueResponse("ping_ok.json")
 
         clientV12.api.ping().execute()
@@ -25,8 +26,9 @@ class SubsonicApiPasswordTest : SubsonicAPIClientTest() {
 
     @Test
     fun `Should pass PasswordHexInterceptor in query params for api version 1 12 0`() {
-        val clientV11 = SubsonicAPIClient(mockWebServerRule.mockWebServer.url("/").toString(),
-                USERNAME, PASSWORD, SubsonicAPIVersions.V1_12_0, CLIENT_ID)
+        val clientV11 = SubsonicAPIClient(
+            config.copy(minimalProtocolVersion = SubsonicAPIVersions.V1_12_0)
+        )
         mockWebServerRule.enqueueResponse("ping_ok.json")
 
         clientV11.api.ping().execute()
diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt
index fb43dbc1..89f8f21d 100644
--- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt
+++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt
@@ -90,7 +90,15 @@ class SubsonicApiSSLTest {
         assertResponseSuccessful(response)
     }
 
-    private fun createSubsonicClient(allowSelfSignedCertificate: Boolean) = SubsonicAPIClient(
-            "https://$HOST:$PORT/", USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID,
-            allowSelfSignedCertificate = allowSelfSignedCertificate)
+    private fun createSubsonicClient(allowSelfSignedCertificate: Boolean): SubsonicAPIClient {
+        val config = SubsonicClientConfiguration(
+            "https://$HOST:$PORT/",
+            USERNAME,
+            PASSWORD,
+            CLIENT_VERSION,
+            CLIENT_ID,
+            allowSelfSignedCertificate = allowSelfSignedCertificate
+        )
+        return SubsonicAPIClient(config)
+    }
 }
diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
index 577e55d2..691a31d3 100644
--- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
+++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
@@ -36,44 +36,39 @@ private const val READ_TIMEOUT = 60_000L
  * @author Yahor Berdnikau
  */
 class SubsonicAPIClient(
-    baseUrl: String,
-    username: String,
-    password: String,
-    minimalProtocolVersion: SubsonicAPIVersions,
-    clientID: String,
-    allowSelfSignedCertificate: Boolean = false,
-    enableLdapUserSupport: Boolean = false,
-    debug: Boolean = false
+    config: SubsonicClientConfiguration,
+    baseOkClient: OkHttpClient = OkHttpClient.Builder().build()
 ) {
-    private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
+    private val versionInterceptor = VersionInterceptor(config.minimalProtocolVersion) {
         protocolVersion = it
     }
 
     private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
-            minimalProtocolVersion,
-            PasswordHexInterceptor(password),
-            PasswordMD5Interceptor(password),
-            enableLdapUserSupport)
+            config.minimalProtocolVersion,
+            PasswordHexInterceptor(config.password),
+            PasswordMD5Interceptor(config.password),
+            config.enableLdapUserSupport
+    )
 
     /**
      * Get currently used protocol version.
      */
-    var protocolVersion = minimalProtocolVersion
+    var protocolVersion = config.minimalProtocolVersion
         private set(value) {
             field = value
             proxyPasswordInterceptor.apiVersion = field
             wrappedApi.currentApiVersion = field
         }
 
-    private val okHttpClient = OkHttpClient.Builder()
+    private val okHttpClient = baseOkClient.newBuilder()
             .readTimeout(READ_TIMEOUT, MILLISECONDS)
-            .apply { if (allowSelfSignedCertificate) allowSelfSignedCertificates() }
+            .apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
             .addInterceptor { chain ->
                 // Adds default request params
                 val originalRequest = chain.request()
                 val newUrl = originalRequest.url().newBuilder()
-                        .addQueryParameter("u", username)
-                        .addQueryParameter("c", clientID)
+                        .addQueryParameter("u", config.username)
+                        .addQueryParameter("c", config.clientID)
                         .addQueryParameter("f", "json")
                         .build()
                 chain.proceed(originalRequest.newBuilder().url(newUrl).build())
@@ -81,7 +76,7 @@ class SubsonicAPIClient(
             .addInterceptor(versionInterceptor)
             .addInterceptor(proxyPasswordInterceptor)
             .addInterceptor(RangeHeaderInterceptor())
-            .apply { if (debug) addLogging() }
+            .apply { if (config.debug) addLogging() }
             .build()
 
     private val jacksonMapper = ObjectMapper()
@@ -90,14 +85,15 @@ class SubsonicAPIClient(
             .registerModule(KotlinModule())
 
     private val retrofit = Retrofit.Builder()
-            .baseUrl("$baseUrl/rest/")
+            .baseUrl("${config.baseUrl}/rest/")
             .client(okHttpClient)
             .addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
             .build()
 
     private val wrappedApi = ApiVersionCheckWrapper(
             retrofit.create(SubsonicAPIDefinition::class.java),
-            minimalProtocolVersion)
+            config.minimalProtocolVersion
+    )
 
     val api: SubsonicAPIDefinition get() = wrappedApi
 
diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt
new file mode 100644
index 00000000..732efe7a
--- /dev/null
+++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt
@@ -0,0 +1,15 @@
+package org.moire.ultrasonic.api.subsonic
+
+/**
+ * Provides configuration for [SubsonicAPIClient].
+ */
+data class SubsonicClientConfiguration(
+    val baseUrl: String,
+    val username: String,
+    val password: String,
+    val minimalProtocolVersion: SubsonicAPIVersions,
+    val clientID: String,
+    val allowSelfSignedCertificate: Boolean = false,
+    val enableLdapUserSupport: Boolean = false,
+    val debug: Boolean = false
+)
diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
new file mode 100644
index 00000000..200b6328
--- /dev/null
+++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
@@ -0,0 +1,10 @@
+package org.moire.ultrasonic.api.subsonic.di
+
+import org.koin.dsl.context.Context
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
+
+const val SUBSONIC_API_CLIENT_CONTEXT = "SubsonicApiClientContext"
+
+fun Context.subsonicApiModule() = context(SUBSONIC_API_CLIENT_CONTEXT) {
+    bean { return@bean SubsonicAPIClient(get(), get()) }
+}
diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle
index 7cd7c804..bf224674 100644
--- a/ultrasonic/build.gradle
+++ b/ultrasonic/build.gradle
@@ -61,6 +61,7 @@ dependencies {
     implementation androidSupport.design
 
     implementation other.kotlinStdlib
+    implementation other.koinAndroid
 
     testImplementation other.kotlinReflect
     testImplementation testing.junit
@@ -69,7 +70,7 @@ dependencies {
     testImplementation testing.kluent
 }
 
-// Excluding all non-kotlin classes
+// Excluding all java classes and stuff that should not be covered
 ext {
     jacocoExclude = [
             '**/activity/**',
@@ -83,7 +84,8 @@ ext {
             '**/view/**',
             '**/R$*.class',
             '**/R.class',
-            '**/BuildConfig.class'
+            '**/BuildConfig.class',
+            '**/di/**'
     ]
 }
 jacocoAndroidUnitTestReport {
diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml
index 6c158318..11730690 100644
--- a/ultrasonic/src/main/AndroidManifest.xml
+++ b/ultrasonic/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
         a:icon="@mipmap/ic_launcher"
         a:roundIcon="@mipmap/ic_launcher_round"
         a:theme="@style/Theme.AppCompat"
+        a:name=".app.UApp"
         a:label="@string/common.appname">
         <activity
             a:name=".activity.MainActivity"
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java
index ccc01852..f2512ba9 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java
@@ -14,6 +14,7 @@ import android.view.View;
 
 import org.moire.ultrasonic.BuildConfig;
 import org.moire.ultrasonic.R;
+import org.moire.ultrasonic.cache.Directories;
 import org.moire.ultrasonic.cache.PermanentFileStorage;
 import org.moire.ultrasonic.service.MusicService;
 import org.moire.ultrasonic.service.MusicServiceFactory;
@@ -282,9 +283,10 @@ public class ServerSettingsFragment extends PreferenceFragment
                 .getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0);
 
         // Clear permanent storage
-        final String storageServerId = MusicServiceFactory.getServerId(sharedPreferences, serverId);
+        final String storageServerId = MusicServiceFactory.getServerId();
+        final Directories directories = MusicServiceFactory.getDirectories();
         final PermanentFileStorage fileStorage = new PermanentFileStorage(
-                MusicServiceFactory.getDirectories(getActivity()),
+                directories,
                 storageServerId,
                 BuildConfig.DEBUG
         );
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java
deleted file mode 100644
index eb859aa5..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- This file is part of Subsonic.
-
- Subsonic is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Subsonic is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
-
- Copyright 2009 (C) Sindre Mehus
- */
-package org.moire.ultrasonic.service;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import org.jetbrains.annotations.NotNull;
-import org.moire.ultrasonic.BuildConfig;
-import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
-import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions;
-import org.moire.ultrasonic.cache.Directories;
-import org.moire.ultrasonic.cache.PermanentFileStorage;
-import org.moire.ultrasonic.util.Constants;
-import org.moire.ultrasonic.util.Util;
-
-import java.io.File;
-
-/**
- * @author Sindre Mehus
- * @version $Id$
- */
-public class MusicServiceFactory {
-    private static final String LOG_TAG = MusicServiceFactory.class.getSimpleName();
-    private static MusicService REST_MUSIC_SERVICE = null;
-    private static MusicService OFFLINE_MUSIC_SERVICE = null;
-
-    public static MusicService getMusicService(Context context) {
-        if (Util.isOffline(context)) {
-            Log.d(LOG_TAG, "App is offline, returning offline music service.");
-            if (OFFLINE_MUSIC_SERVICE == null) {
-                synchronized (MusicServiceFactory.class) {
-                    if (OFFLINE_MUSIC_SERVICE == null) {
-                        Log.d(LOG_TAG, "Creating new offline music service");
-                        OFFLINE_MUSIC_SERVICE = new OfflineMusicService(
-                                createSubsonicApiClient(context),
-                                getPermanentFileStorage(context));
-                    }
-                }
-            }
-
-            return OFFLINE_MUSIC_SERVICE;
-        } else {
-            Log.d(LOG_TAG, "Returning rest music service");
-            if (REST_MUSIC_SERVICE == null) {
-                synchronized (MusicServiceFactory.class) {
-                    if (REST_MUSIC_SERVICE == null) {
-                        Log.d(LOG_TAG, "Creating new rest music service");
-                        REST_MUSIC_SERVICE = new CachedMusicService(new RESTMusicService(
-                                createSubsonicApiClient(context),
-                                getPermanentFileStorage(context)));
-                    }
-                }
-            }
-
-            return REST_MUSIC_SERVICE;
-        }
-    }
-
-    /**
-     * Resets {@link MusicService} to initial state, so on next call to {@link #getMusicService(Context)}
-     * it will return updated instance of it.
-     */
-    public static void resetMusicService() {
-        Log.d(LOG_TAG, "Resetting music service");
-        synchronized (MusicServiceFactory.class) {
-            REST_MUSIC_SERVICE = null;
-            OFFLINE_MUSIC_SERVICE = null;
-        }
-    }
-
-    private static SubsonicAPIClient createSubsonicApiClient(final Context context) {
-        final SharedPreferences preferences = Util.getPreferences(context);
-        int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
-        String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
-        String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
-        String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
-        boolean allowSelfSignedCertificate = preferences
-                .getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false);
-        boolean enableLdapUserSupport = preferences
-                .getBoolean(Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance , false);
-
-        if (serverUrl == null ||
-                username == null ||
-                password == null) {
-            Log.i("MusicServiceFactory", "Server credentials is not available");
-            return new SubsonicAPIClient("http://localhost", "", "",
-                    SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
-                    Constants.REST_CLIENT_ID, allowSelfSignedCertificate,
-                    enableLdapUserSupport, BuildConfig.DEBUG);
-        }
-
-        return new SubsonicAPIClient(serverUrl, username, password,
-                SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
-                Constants.REST_CLIENT_ID, allowSelfSignedCertificate,
-                enableLdapUserSupport, BuildConfig.DEBUG);
-    }
-
-    private static PermanentFileStorage getPermanentFileStorage(final Context context) {
-        final SharedPreferences preferences = Util.getPreferences(context);
-        int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
-        final String serverId = getServerId(preferences, instance);
-
-        return new PermanentFileStorage(getDirectories(context), serverId, BuildConfig.DEBUG);
-    }
-
-    public static String getServerId(final SharedPreferences sp, final int instance) {
-        String serverUrl = sp.getString(
-                Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
-        return String.valueOf(Math.abs((serverUrl + instance).hashCode()));
-    }
-
-    public static Directories getDirectories(final Context context) {
-        return new Directories() {
-            @NotNull
-            @Override
-            public File getInternalCacheDir() {
-                return context.getCacheDir();
-            }
-
-            @NotNull
-            @Override
-            public File getInternalDataDir() {
-                return context.getFilesDir();
-            }
-
-            @Override
-            public File getExternalCacheDir() {
-                return context.getExternalCacheDir();
-            }
-        };
-    }
-}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
index 90620e00..e9673e23 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
@@ -171,14 +171,16 @@ public class Util extends DownloadActivity
 		return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS, false);
 	}
 
-	public static void setActiveServer(Context context, int instance)
-	{
+    public static void setActiveServer(
+            Context context,
+            int instance
+    ) {
         MusicServiceFactory.resetMusicService();
-		SharedPreferences preferences = getPreferences(context);
-		SharedPreferences.Editor editor = preferences.edit();
-		editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
-		editor.commit();
-	}
+        SharedPreferences preferences = getPreferences(context);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
+        editor.apply();
+    }
 
 	public static int getActiveServer(Context context)
 	{
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
new file mode 100644
index 00000000..f8bb9d24
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt
@@ -0,0 +1,21 @@
+package org.moire.ultrasonic.app
+
+import android.app.Application
+import org.koin.android.ext.android.startKoin
+import org.moire.ultrasonic.di.baseNetworkModule
+import org.moire.ultrasonic.di.directoriesModule
+import org.moire.ultrasonic.di.musicServiceModule
+import org.moire.ultrasonic.util.Util
+
+class UApp : Application() {
+    override fun onCreate() {
+        super.onCreate()
+
+        val sharedPreferences = Util.getPreferences(this)
+        startKoin(this, listOf(
+            directoriesModule,
+            baseNetworkModule,
+            musicServiceModule(sharedPreferences)
+        ))
+    }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt
new file mode 100644
index 00000000..d8a815a7
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/cache/AndroidDirectories.kt
@@ -0,0 +1,17 @@
+package org.moire.ultrasonic.cache
+
+import android.content.Context
+import java.io.File
+
+/**
+ * Provides specific to Android implementation of [Directories].
+ */
+class AndroidDirectories(
+    private val context: Context
+) : Directories {
+    override fun getInternalCacheDir(): File = context.cacheDir
+
+    override fun getInternalDataDir(): File = context.filesDir
+
+    override fun getExternalCacheDir(): File? = context.externalCacheDir
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt
new file mode 100644
index 00000000..0455ff53
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/BaseNetworkModule.kt
@@ -0,0 +1,11 @@
+package org.moire.ultrasonic.di
+
+import okhttp3.OkHttpClient
+import org.koin.dsl.module.applicationContext
+
+/**
+ * Provides base network dependencies.
+ */
+val baseNetworkModule = applicationContext {
+    bean { OkHttpClient.Builder().build() }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt
new file mode 100644
index 00000000..ddbe21f0
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/DirectoriesModule.kt
@@ -0,0 +1,9 @@
+package org.moire.ultrasonic.di
+
+import org.koin.dsl.module.applicationContext
+import org.moire.ultrasonic.cache.AndroidDirectories
+import org.moire.ultrasonic.cache.Directories
+
+val directoriesModule = applicationContext {
+    bean { AndroidDirectories(get()) } bind Directories::class
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
new file mode 100644
index 00000000..884b4988
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
@@ -0,0 +1,113 @@
+@file:JvmName("MusicServiceModule")
+package org.moire.ultrasonic.di
+
+import android.content.SharedPreferences
+import android.util.Log
+import org.koin.dsl.module.applicationContext
+import org.moire.ultrasonic.BuildConfig
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
+import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
+import org.moire.ultrasonic.api.subsonic.di.subsonicApiModule
+import org.moire.ultrasonic.cache.PermanentFileStorage
+import org.moire.ultrasonic.service.CachedMusicService
+import org.moire.ultrasonic.service.MusicService
+import org.moire.ultrasonic.service.OfflineMusicService
+import org.moire.ultrasonic.service.RESTMusicService
+import org.moire.ultrasonic.util.Constants
+import kotlin.math.abs
+
+internal const val MUSIC_SERVICE_CONTEXT = "CurrentMusicService"
+internal const val ONLINE_MUSIC_SERVICE = "OnlineMusicService"
+internal const val OFFLINE_MUSIC_SERVICE = "OfflineMusicService"
+private const val DEFAULT_SERVER_INSTANCE = 1
+private const val UNKNOWN_SERVER_URL = "not-exists"
+private const val LOG_TAG = "MusicServiceModule"
+
+fun musicServiceModule(sp: SharedPreferences) = applicationContext {
+    context(MUSIC_SERVICE_CONTEXT) {
+        subsonicApiModule()
+
+        bean(name = "ServerInstance") {
+            return@bean sp.getInt(
+                Constants.PREFERENCES_KEY_SERVER_INSTANCE,
+                DEFAULT_SERVER_INSTANCE
+            )
+        }
+
+        bean(name = "ServerID") {
+            val serverInstance = get<Int>(name = "ServerInstance")
+            val serverUrl = sp.getString(
+                Constants.PREFERENCES_KEY_SERVER_URL + serverInstance,
+                null
+            )
+            return@bean if (serverUrl == null) {
+                UNKNOWN_SERVER_URL
+            } else {
+                abs("$serverUrl$serverInstance".hashCode()).toString()
+            }
+        }
+
+        bean {
+            val serverId = get<String>(name = "ServerID")
+            return@bean PermanentFileStorage(get(), serverId, BuildConfig.DEBUG)
+        }
+
+        bean {
+            val instance = get<Int>(name = "ServerInstance")
+            val serverUrl = sp.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null)
+            val username = sp.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null)
+            val password = sp.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null)
+            val allowSelfSignedCertificate = sp.getBoolean(
+                Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance,
+                false
+            )
+            val enableLdapUserSupport = sp.getBoolean(
+                Constants.PREFERENCES_KEY_LDAP_SUPPORT + instance,
+                false
+            )
+
+            if (serverUrl == null ||
+                username == null ||
+                password == null
+            ) {
+                Log.i(LOG_TAG, "Server credentials is not available")
+                return@bean SubsonicClientConfiguration(
+                    baseUrl = "http://localhost",
+                    username = "",
+                    password = "",
+                    minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion(
+                        Constants.REST_PROTOCOL_VERSION
+                    ),
+                    clientID = Constants.REST_CLIENT_ID,
+                    allowSelfSignedCertificate = allowSelfSignedCertificate,
+                    enableLdapUserSupport = enableLdapUserSupport,
+                    debug = BuildConfig.DEBUG
+                )
+            } else {
+                return@bean SubsonicClientConfiguration(
+                    baseUrl = serverUrl,
+                    username = username,
+                    password = password,
+                    minimalProtocolVersion = SubsonicAPIVersions.fromApiVersion(
+                        Constants.REST_PROTOCOL_VERSION
+                    ),
+                    clientID = Constants.REST_CLIENT_ID,
+                    allowSelfSignedCertificate = allowSelfSignedCertificate,
+                    enableLdapUserSupport = enableLdapUserSupport,
+                    debug = BuildConfig.DEBUG
+                )
+            }
+        }
+
+        bean { return@bean SubsonicAPIClient(get()) }
+
+        bean<MusicService>(name = ONLINE_MUSIC_SERVICE) {
+            return@bean CachedMusicService(RESTMusicService(get(), get()))
+        }
+
+        bean<MusicService>(name = OFFLINE_MUSIC_SERVICE) {
+            return@bean OfflineMusicService(get(), get())
+        }
+    }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt
new file mode 100644
index 00000000..5fbb393e
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt
@@ -0,0 +1,56 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package org.moire.ultrasonic.service
+
+import android.content.Context
+import org.koin.standalone.KoinComponent
+import org.koin.standalone.get
+import org.koin.standalone.releaseContext
+import org.moire.ultrasonic.cache.Directories
+import org.moire.ultrasonic.di.MUSIC_SERVICE_CONTEXT
+import org.moire.ultrasonic.di.OFFLINE_MUSIC_SERVICE
+import org.moire.ultrasonic.di.ONLINE_MUSIC_SERVICE
+import org.moire.ultrasonic.util.Util
+
+@Deprecated("Use DI way to get MusicService")
+object MusicServiceFactory : KoinComponent {
+    @JvmStatic
+    fun getMusicService(context: Context): MusicService {
+        return if (Util.isOffline(context)) {
+            get(OFFLINE_MUSIC_SERVICE)
+        } else {
+            get(ONLINE_MUSIC_SERVICE)
+        }
+    }
+
+    /**
+     * Resets [MusicService] to initial state, so on next call to [.getMusicService]
+     * it will return updated instance of it.
+     */
+    @JvmStatic
+    fun resetMusicService() {
+        releaseContext(MUSIC_SERVICE_CONTEXT)
+    }
+
+    @JvmStatic
+    fun getServerId() = get<String>(name = "ServerID")
+
+    @JvmStatic
+    fun getDirectories() = get<Directories>()
+}