I am doing end-to-end test which use WorkManager in my Application. My DelegatingWorkerFactory() is hilt injected and creates the different factories, which in turn create the different ListenableWorkers in my app.
For testing, I would like to bind to these workers to the test version i.e. TestListenableWorker(). What is the best way to do this using Hilt?
Appreciate there could be XY problem here and there might be a different approach to achieve this. Essentially I just want to seamlessly switch to TestListenableWorkers with hilt injection.
#Singleton
class MyDelegatingWorkerFactory #Inject constructor(
myRepository: MyRepository
): DelegatingWorkerFactory() {
init {
addFactory(WorkerGetDataFactory(myRepository))
addFactory(WorkerOnReceiverFactory(myRepository))
addFactory(WorkerUpdateStateFactory(myRepository))
}
}
class WorkerGetDataFactory(
private val myRepository: MyRepository
): WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters,
): ListenableWorker? { /*<---- how can I use Hilt to implement TestListenableWorker instance*/
return when (workerClassName) {
WorkerGetData::class.java.name ->
WorkerGetData(myRepository, appContext, workerParameters, )
else ->
null
}
}
}
I am having problem trying to initialize my WorkerGetData class for instrumentation testing. I have done the following:
removed the default work initializer in manifest file.
added configuration provider in the Application class.
called WorkManagerTestInitHelper in the Test file.
added kapt "androidx.hilt:hilt-compiler:1.0.0" to app module.
added kapt "com.google.dagger:hilt-compiler:2.42" to app module.
But still get the error java.lang.NoSuchMethodException: com.example.myproject.WorkerGetData.<init> [class android.content.Context, class androidx.work.WorkerParameters].
I am using work manager version 2.7.1.
Code
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
#HiltAndroidApp
class Application : android.app.Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
}
}
#HiltWorker
class WorkerGetData #AssistedInject constructor(
val repository: MyRepository,
#Assisted val context: Context,
#Assisted workerParameters: WorkerParameters,
): CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
val data = repository.getData()
return Result.success(data)
}
}
#Test
fun testGetDataWorker() {
val request = OneTimeWorkRequestBuilder<WorkerGetData>()
.build()
val workManager = WorkManager.getInstance(context)
workManager.enqueue(request).result.get() /*<-----------------------error here*/
val workInfo = workManager.getWorkInfoById(request.id).get()
assertThat(workInfo.state).isEqualTo(WorkInfo.State.SUCCEEDED)
}
There was a 6th item which I had forgot to do. I needed to create a WorkerFatory() so that Hilt could correctly inject WorkerGetData constructor. There is a great article from Pietro Maggi on how to do this:
https://medium.com/androiddevelopers/customizing-workmanager-with-dagger-1029688c0978
This question is about a Kotlin JS project which uses the Kotlin Frontend Plugin.
I want to use some UI components from the Vaadin Components library.
I have two questions about this:
(1) What would be the best way to include web components in Kotlin JS
=> for my complete code, see the link to the source below. In summary the relevant details are:
build.gradle.kts
kotlinFrontend {
npm {
dependency("#vaadin/vaadin-grid")
}
}
vaadin.grid.Imports.kt
#file:JsModule("#vaadin/vaadin-grid")
#file:JsNonModule
package vaadin.grid
external class GridElement {
companion object
}
Why the companion object? I need it for the workaround (see below).
foo.kt
fun main() {
document.getElementById("container")!!.append {
vaadin_grid {
attributes["id"] = "grid"
}
}
initUI()
}
fun initUI() {
// Force the side-effects of the vaadin modules. Is there a better way?
console.log(GridElement)
val grid = document.querySelector("#grid") /* ?? as GridElement ?? */
}
The console.log is the ugly workaround trick I want to avoid. If I don't do anything with GridElement then it's just not included in my bundle.
The vaadin_grid DSL is defined as a custom kotlinx.html tag which is unrelated code.
(2) I want to keep my code as typed as possible to avoid asDynamic but when I cast the HTMLElement to a Vaadin Element I get ClassCastExceptions (because GridElement is undefined).
For example I want to write something like this:
val grid : GridElement = document.querySelector("#grid") as GridElement
grid.items = ... // vs grid.asDynamic().items which does work
Here is how I define the external GridElement
vaadin/button/Imports.kt
#file:JsModule("#vaadin/vaadin-grid")
#file:JsNonModule
package vaadin.grid
import org.w3c.dom.HTMLElement
abstract external class GridElement : HTMLElement {
var items: Array<*> = definedExternally
}
build/node_modules/#vaadin/vaadin-grid/src/vaadin-grid.js
...
customElements.define(GridElement.is, GridElement);
export { GridElement };
Source example
To run:
From the root of the git repo:
./gradlew 05-kt-frontend-vaadin:build && open 05-kt-frontend-vaadin/frontend.html
I found the answer(s)
For the first question
(1) What would be the best way to include web components in Kotlin JS
Instead of the console.log to trigger the side effects I use require(...)
external fun require(module: String): dynamic
fun main() {
require("#vaadin/vaadin-button")
require("#vaadin/vaadin-text-field")
require("#vaadin/vaadin-grid")
...
}
(credits to someone's answer on the kotlin-frontend-plugin list)
(2) I want to keep my code as typed as possible to avoid asDynamic
Instead of importing #vaadin/vaadin-grid I have to import the file which actually exposes the element. Then it seems to work and I can even add generics to my GridElement:
#file:JsModule("#vaadin/vaadin-grid/src/vaadin-grid")
#file:JsNonModule
package vaadin.grid
import org.w3c.dom.HTMLElement
abstract external class GridElement<T> : HTMLElement {
var items: Array<out T> = definedExternally
}
This way I was able to get rid of all the asDynamics
val firstNameField = document.querySelector("#firstName") as TextFieldElement?
val lastNameField = document.querySelector("#lastName") as TextFieldElement?
val addButton = document.querySelector("#addButton") as ButtonElement?
val grid = document.querySelector("#grid") as GridElement<Person>?
val initialPeople: Array<out Person> = emptyArray()
grid?.items = initialPeople
addButton?.addEventListener("click", {
// Read the new person's data
val person = Person(firstNameField?.value, lastNameField?.value)
// Add it to the items
if(grid != null){
val people = grid.items
grid.items = people.plus(person)
}
// Reset the form fields
firstNameField?.value = ""
lastNameField?.value = ""
})
I just updated my Android Studio to version 3.2 and followed instructions to use androidx.
I've been using a Youtube fragment inside a Fragment activity and everything worked perfectly but, after the update, these 3 simple lines now give me the error "Cannot resolve method 'add(...)'":
YouTubePlayerSupportFragment youTubePlayerFragment = YouTubePlayerSupportFragment.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
...and when i try to use "replace" instead of "add" it says: "Wrong 2nd argument type. Found: 'com.google.android.youtube.player.YouTubePlayerSupportFragment', required: 'androidx.fragment.app.Fragment'"
...which makes me think that the problem has to do with the new AndroidX feature.
The problem is that the add method wants the second parameter of type:
androidx.fragment.app.Fragment
...but the YouTubePlayerSupportFragment returns a:
android.support.v4.app.Fragment
Does anyone know how to solve this problem?
Is there a way to cast the "android.support.v4.app.Fragment" into the "androidx.fragment.app.Fragment"?
Just use transaction.replace. Ignore the error, it'll work. Google hasn't refactored youtube api library to androidx yet.
Just copy the original java file (com.google.android.youtube.player.YouTubePlayerFragment) to your project to the same package but different class name etc. com.google.android.youtube.player.YouTubePlayerFragmentX, and update the extends class from android.app.Fragment to androidx.fragment.app.Fragment.
The implementation is the same:
YouTubePlayerFragmentX youTubePlayerFragment = YouTubePlayerFragmentX.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
Tested... it's working.
I've fixed it by following the #Hosszful answer,
I made it easy by just using this file, https://gist.github.com/medyo/f226b967213c3b8ec6f6bebb5338a492
Replace .add
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
with this .replace
transaction.replace(R.id.youtube_fragment, youTubePlayerFragment).commit();
and copy this class to your project folder (it may need to create the following folders)
java -> com -> google -> android -> youtube -> player -> (here name of)
YouTubePlayerSupportFragmentX.java
then in code replace
YouTubePlayerSupportFragment
to
YouTubePlayerSupportFragmentX.
Many thanks to both #Hosszuful and #Mehdi. I have followed your advice and it worked very nicely.
A few weeks after I asked this question I "translated" my app to Kotlin and, therefore, I tried to translate your answer as well.
This is what I ended up with and it's working for me.
package com.google.android.youtube.player //<--- IMPORTANT!!!!
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.internal.ab
import java.util.*
class YouTubePlayerSupportFragmentX : Fragment(), YouTubePlayer.Provider {
private val a = ViewBundle()
private var b: Bundle? = null
private var c: YouTubePlayerView? = null
private var d: String? = null
private var e: YouTubePlayer.OnInitializedListener? = null
override fun initialize(var1: String, var2: YouTubePlayer.OnInitializedListener) {
d = ab.a(var1, "Developer key cannot be null or empty")
e = var2
a()
}
private fun a() {
if (c != null && e != null) {
c?.a(this.activity, this, d, e, b)
b = null
e = null
}
}
override fun onCreate(var1: Bundle?) {
super.onCreate(var1)
b = var1?.getBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE")
}
override fun onCreateView(var1: LayoutInflater, var2: ViewGroup?, var3: Bundle?): android.view.View? {
c = YouTubePlayerView(Objects.requireNonNull(this.activity), null, 0, a)
a()
return c
}
override fun onStart() {
super.onStart()
c?.a()
}
override fun onResume() {
super.onResume()
c?.b()
}
override fun onPause() {
c?.c()
super.onPause()
}
override fun onSaveInstanceState(var1: Bundle) {
super.onSaveInstanceState(var1)
(if (c != null) c?.e() else b)?.let { var2 ->
var1.putBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE", var2)
}
}
override fun onStop() {
c?.d()
super.onStop()
}
override fun onDestroyView() {
this.activity?.let { c?.c(it.isFinishing) }
c = null
super.onDestroyView()
}
override fun onDestroy() {
if (c != null) {
val var1 = this.activity
c?.b(var1 == null || var1.isFinishing)
}
super.onDestroy()
}
private inner class ViewBundle : YouTubePlayerView.b {
override fun a(var1: YouTubePlayerView, var2: String, var3: YouTubePlayer.OnInitializedListener) {
e?.let { initialize(var2, it) }
}
override fun a(var1: YouTubePlayerView) {}
}
companion object {
fun newInstance(): YouTubePlayerSupportFragmentX {
return YouTubePlayerSupportFragmentX()
}
}
}
There may be better ways to write it down and any help on that regard would be mostly appreciated but, if anyone else was looking for the Kotlin version of this problem's solution, this code should do.
PS: I'm gonna leave #Mehdi's answer as the accepted one because he's also sharing credits with #Hosszuful and because my answer is just the Kotlin version of what they suggest.
I got it working by following code chunk.
Object obj =
getSupportFragmentManager().findFragmentById(R.id.youtube_player_fragment);
if (obj instanceof YouTubePlayerSupportFragment)
youTubePlayerFragment = (YouTubePlayerSupportFragment) obj;
During debugging I found that the fragmentmanager was coming to be instance of YouTubePlayerSupportFragment only. But compiler was not able to cast it when I would write
(YouTubePlayerSupportFragment)
getSupportFragmentManager().findFragmentById(R.id.youtube_player_fragment);
The above code chunk (instanceof ) worked fine.
Suggested solutions did not work, till I tried the comment from Bek: Pierfrancesco Soffritti's android-youtube-player that is maintained and works without a hitch.
I would like to use the nested Generics, like
class Class<List<T>> {
...
}
But always Dart Editor gives me alerts. How should I avoid these alerts?
Well, Dart Editor is right. This code doesn't make any sense. Without further information on what you are trying to do (don't hesitate to update your question), I am assuming you actually mean one of those:
class MyClass<T> {
List<T> listField;
// other stuff
}
Or maybe the list itself should be generic?
void main() {
MyClass<SomeCustomListClass<String>> instance = new MyClass();
}
class MyClass<T extends List<String>> {
T listField;
// ...
}
Or maybe everything has to be generic:
void main() {
MyClass<String, SomeCustomListClass<String>> instance = new MyClass();
}
class MyClass<TElement, TList extends List<TElement>> {
TList listField;
TElement _firstListElement;
// whatever that could be used for
}