Create ktx initLoader / restartLoader methods

Rather than create a LoaderManager.LoaderCallbacks
instance, allow Kotlin users to pass in the Loader
directly and lambdas for managing the callback
methods.

Test: newly added test
Change-Id: I52e87a7f55f59a24f63362893b6c1e09af25b08a
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 480f2ba..3ca5070 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -79,6 +79,7 @@
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-ktx-lint")
     prebuilts(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate", "1.0.0-alpha01")
     prebuilts(LibraryGroups.LIFECYCLE, "2.2.0-alpha01")
+    ignore(LibraryGroups.LOADER.group, "loader-ktx")
     prebuilts(LibraryGroups.LOADER, "1.1.0-rc01")
     prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
     prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-beta02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 40699e7..36f66cf 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -100,6 +100,7 @@
 const val ARCH_LIFECYCLE_LIVEDATA = "androidx.lifecycle:lifecycle-livedata:2.0.0"
 const val ARCH_LIFECYCLE_SERVICE = "androidx.lifecycle:lifecycle-service:2.0.0"
 const val ARCH_LIFECYCLE_VIEWMODEL = "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
+const val ARCH_LIFECYCLE_VIEWMODEL_KTX = "androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0"
 const val ARCH_LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:2.0.0"
 const val ARCH_CORE_COMMON = "androidx.arch.core:core-common:2.0.1@jar"
 const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.1"
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 452adad..d4f59b3 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -2043,6 +2043,18 @@
     },
     {
       "from": {
+        "groupId": "com.android.support",
+        "artifactId": "loader-ktx",
+        "version": "{oldSlVersion}"
+      },
+      "to": {
+        "groupId": "androidx.loader",
+        "artifactId": "loader-ktx",
+        "version": "{newSlVersion}"
+      }
+    },
+    {
+      "from": {
         "groupId": "androidx.localbroadcastmanager",
         "artifactId": "localbroadcastmanager",
         "version": "{newSlVersion}"
diff --git a/loader/loader-ktx/OWNERS b/loader/loader-ktx/OWNERS
new file mode 100644
index 0000000..e450f4c
--- /dev/null
+++ b/loader/loader-ktx/OWNERS
@@ -0,0 +1 @@
[email protected]
diff --git a/loader/loader-ktx/api/1.2.0-alpha01.txt b/loader/loader-ktx/api/1.2.0-alpha01.txt
new file mode 100644
index 0000000..132ba80
--- /dev/null
+++ b/loader/loader-ktx/api/1.2.0-alpha01.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.loader.app {
+
+  public final class LoaderManagerKt {
+    ctor public LoaderManagerKt();
+    method @MainThread public static inline <D> void initLoader(androidx.loader.app.LoaderManager, int id, androidx.loader.content.Loader<D> loader, kotlin.jvm.functions.Function0<kotlin.Unit> onLoaderReset = {}, kotlin.jvm.functions.Function1<? super D,kotlin.Unit> onLoadFinished);
+    method @MainThread public static inline <D> void restartLoader(androidx.loader.app.LoaderManager, int id, androidx.loader.content.Loader<D> loader, kotlin.jvm.functions.Function0<kotlin.Unit> onLoaderReset = {}, kotlin.jvm.functions.Function1<? super D,kotlin.Unit> onLoadFinished);
+  }
+
+}
+
diff --git a/loader/loader-ktx/api/current.txt b/loader/loader-ktx/api/current.txt
new file mode 100644
index 0000000..132ba80
--- /dev/null
+++ b/loader/loader-ktx/api/current.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.loader.app {
+
+  public final class LoaderManagerKt {
+    ctor public LoaderManagerKt();
+    method @MainThread public static inline <D> void initLoader(androidx.loader.app.LoaderManager, int id, androidx.loader.content.Loader<D> loader, kotlin.jvm.functions.Function0<kotlin.Unit> onLoaderReset = {}, kotlin.jvm.functions.Function1<? super D,kotlin.Unit> onLoadFinished);
+    method @MainThread public static inline <D> void restartLoader(androidx.loader.app.LoaderManager, int id, androidx.loader.content.Loader<D> loader, kotlin.jvm.functions.Function0<kotlin.Unit> onLoaderReset = {}, kotlin.jvm.functions.Function1<? super D,kotlin.Unit> onLoadFinished);
+  }
+
+}
+
diff --git a/loader/loader-ktx/api/res-1.2.0-alpha01.txt b/loader/loader-ktx/api/res-1.2.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/loader/loader-ktx/api/res-1.2.0-alpha01.txt
diff --git a/loader/loader-ktx/api/restricted_1.2.0-alpha01.txt b/loader/loader-ktx/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/loader/loader-ktx/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/loader/loader-ktx/api/restricted_current.txt b/loader/loader-ktx/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/loader/loader-ktx/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/loader/loader-ktx/build.gradle b/loader/loader-ktx/build.gradle
new file mode 100644
index 0000000..7e007b9
--- /dev/null
+++ b/loader/loader-ktx/build.gradle
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    buildTypes {
+        debug {
+            testCoverageEnabled = false // Breaks Kotlin compiler.
+        }
+    }
+}
+
+dependencies {
+    api(project(":loader:loader"))
+    api(ARCH_LIFECYCLE_VIEWMODEL_KTX) {
+        because 'Mirror loader dependency graph for -ktx artifacts'
+    }
+    api(KOTLIN_STDLIB)
+
+    androidTestImplementation(TRUTH)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+}
+
+androidx {
+    name = "Loader Kotlin Extensions"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenVersion = LibraryVersions.LOADER
+    mavenGroup = LibraryGroups.LOADER
+    inceptionYear = "2019"
+    description = "Kotlin extensions for 'loader' artifact"
+}
diff --git a/loader/loader-ktx/src/androidTest/AndroidManifest.xml b/loader/loader-ktx/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..a5bee33
--- /dev/null
+++ b/loader/loader-ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.loader.ktx">
+    <application>
+    </application>
+</manifest>
diff --git a/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt
new file mode 100644
index 0000000..3a92843
--- /dev/null
+++ b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/LoaderManagerTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.loader.app
+
+import android.content.Context
+import androidx.loader.app.test.LoaderOwner
+import androidx.loader.content.Loader
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class LoaderManagerTest {
+
+    private lateinit var loaderManager: LoaderManager
+
+    @Before
+    fun setup() {
+        loaderManager = LoaderManager.getInstance(LoaderOwner())
+    }
+
+    @Test
+    fun testInitLoader() {
+        val countDownLatch = CountDownLatch(1)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            loaderManager.initLoader(0, StringLoader()) {
+                countDownLatch.countDown()
+            }
+        }
+        assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue()
+    }
+
+    @Test
+    fun testInitLoaderWithReset() {
+        val countDownLatch = CountDownLatch(2)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            loaderManager.initLoader(0, StringLoader(), { countDownLatch.countDown() }) {
+                countDownLatch.countDown()
+            }
+            loaderManager.destroyLoader(0)
+        }
+        assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue()
+    }
+
+    @Test
+    fun testRestartLoader() {
+        val countDownLatch = CountDownLatch(1)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            loaderManager.restartLoader(0, StringLoader()) {
+                countDownLatch.countDown()
+            }
+        }
+        assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue()
+    }
+
+    class StringLoader : Loader<String>(mock(Context::class.java)) {
+        override fun onStartLoading() {
+            deliverResult("test")
+        }
+    }
+}
diff --git a/loader/loader-ktx/src/androidTest/java/androidx/loader/app/test/LoaderOwner.kt b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/test/LoaderOwner.kt
new file mode 100644
index 0000000..7fb6a10
--- /dev/null
+++ b/loader/loader-ktx/src/androidTest/java/androidx/loader/app/test/LoaderOwner.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.loader.app.test
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.ViewModelStoreOwner
+
+class LoaderOwner : LifecycleOwner, ViewModelStoreOwner {
+
+    private val lifecycle = LifecycleRegistry(this)
+    private val viewModelStore = ViewModelStore()
+
+    init {
+        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
+    }
+
+    override fun getLifecycle() = lifecycle
+
+    override fun getViewModelStore() = viewModelStore
+}
diff --git a/loader/loader-ktx/src/main/AndroidManifest.xml b/loader/loader-ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5937e29
--- /dev/null
+++ b/loader/loader-ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest package="androidx.loader.ktx"/>
diff --git a/loader/loader-ktx/src/main/java/androidx/loader/app/LoaderManager.kt b/loader/loader-ktx/src/main/java/androidx/loader/app/LoaderManager.kt
new file mode 100644
index 0000000..7269c3d
--- /dev/null
+++ b/loader/loader-ktx/src/main/java/androidx/loader/app/LoaderManager.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.loader.app
+
+import android.os.Bundle
+import androidx.annotation.MainThread
+import androidx.loader.content.Loader
+
+/**
+ * Ensures a loader is initialized and active.  If the loader doesn't
+ * already exist, the given [loader] is used and (if the activity/fragment is currently
+ * started) starts the loader.  Otherwise the last created
+ * loader is re-used and [loader] is ignored.
+ *
+ * In either case, the given [onLoaderReset] and [onLoadFinished] methods will be
+ * associated with the loader, and
+ * will be called as the loader state changes.  If at the point of call
+ * the caller is in its started state, and the requested loader
+ * already exists and has generated its data, then
+ * callback [onLoadFinished] will
+ * be called immediately (inside of this function), so you must be prepared
+ * for this to happen.
+ *
+ * ```
+ * LoaderManager.getInstance(this).initLoader(LOADER_ID, MyLoader()) { data ->
+ *   // Handle onLoadFinished
+ * }
+ *
+ * // Or, if you need an onLoaderReset callback:
+ * LoaderManager.getInstance(this).initLoader(LOADER_ID, MyLoader(), {
+ *   // Handle onLoaderReset
+ * }) { data ->
+ *   // Handle onLoadFinished
+ * }
+ * ```
+ *
+ * @param id A unique identifier for this loader.  Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param loader The [Loader] to be used when no loader is already created with this [id].
+ * @param onLoaderReset Lambda to call to handle [LoaderManager.LoaderCallbacks.onLoaderReset]
+ * @param onLoadFinished Lambda to call to handle [LoaderManager.LoaderCallbacks.onLoadFinished]
+ */
+@MainThread
+inline fun <D> LoaderManager.initLoader(
+    id: Int,
+    loader: Loader<D>,
+    crossinline onLoaderReset: () -> Unit = {},
+    crossinline onLoadFinished: (data: D) -> Unit
+) {
+    initLoader(id, null, object : LoaderManager.LoaderCallbacks<D> {
+        override fun onCreateLoader(id: Int, args: Bundle?) = loader
+
+        override fun onLoadFinished(loader: Loader<D>, data: D) {
+            onLoadFinished(data)
+        }
+
+        override fun onLoaderReset(loader: Loader<D>) {
+            onLoaderReset()
+        }
+    })
+}
+
+/**
+ * Starts a new or restarts an existing [Loader] in
+ * this manager, registers the given [onLoaderReset] and [onLoadFinished] methods to it,
+ * and (if the activity/fragment is currently started) starts loading it.
+ * If a loader with the same id has previously been
+ * started it will automatically be destroyed when the new loader completes
+ * its work.
+ *
+ * ```
+ * LoaderManager.getInstance(this).restartLoader(LOADER_ID, MyLoader()) { data ->
+ *   // Handle onLoadFinished
+ * }
+ *
+ * // Or, if you need an onLoaderReset callback:
+ * LoaderManager.getInstance(this).restartLoader(LOADER_ID, MyLoader(), {
+ *   // Handle onLoaderReset
+ * }) { data ->
+ *   // Handle onLoadFinished
+ * }
+ * ```
+ *
+ * @param id A unique identifier for this loader.  Can be whatever you want.
+ * Identifiers are scoped to a particular LoaderManager instance.
+ * @param loader The [Loader] to be used
+ * @param onLoaderReset Lambda to call to handle [LoaderManager.LoaderCallbacks.onLoaderReset]
+ * @param onLoadFinished Lambda to call to handle [LoaderManager.LoaderCallbacks.onLoadFinished]
+ */
+@MainThread
+inline fun <D> LoaderManager.restartLoader(
+    id: Int,
+    loader: Loader<D>,
+    crossinline onLoaderReset: () -> Unit = {},
+    crossinline onLoadFinished: (data: D) -> Unit
+) {
+    restartLoader(id, null, object : LoaderManager.LoaderCallbacks<D> {
+        override fun onCreateLoader(id: Int, args: Bundle?) = loader
+
+        override fun onLoadFinished(loader: Loader<D>, data: D) {
+            onLoadFinished(data)
+        }
+
+        override fun onLoaderReset(loader: Loader<D>) {
+            onLoaderReset()
+        }
+    })
+}
diff --git a/settings.gradle b/settings.gradle
index a3cb6f2..4a14a1f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -127,6 +127,7 @@
 includeProject(":lifecycle:lifecycle-viewmodel-ktx", "lifecycle/viewmodel/ktx")
 includeProject(":lifecycle:lifecycle-viewmodel-savedstate","lifecycle/viewmodel-savedstate")
 includeProject(":loader:loader", "loader/loader")
+includeProject(":loader:loader-ktx", "loader/loader-ktx")
 includeProject(":localbroadcastmanager", "localbroadcastmanager")
 includeProject(":media", "media")
 includeProject(":media2:media2-common", "media2/common")