Kotlin and Android Development featuring Jetpack: Transformations class doesn't seem to exist anymore (pp. 140-141

@mfazio23

Android Studio will not accept anything I do when trying to use the Transformations class, as described on pp. 140-141. Googling around reveals that ANOTHER change has been made, and that Transformations no long exists as such but is now built-into Kotlin in some way. I found an example about how to change Transformations.mapswitch() to someClass.mapswitch() but no complete example about how to do the same with Transformations.map(). My efforts to apply the same format to all of the Transformations.map() statements just changed the error from “Unresolved reference: Transformations” to “Type mismatch. Required: Player. Found: Player?”, for instance. This despite setting “lc_version” to “2.3.0”.

What should I do to get Chapter 5’s code working?

Thanks in advance!

tl;dr: Change to calling map { ... } on the LiveData object, change the variable to its nullable version.

It seems like you are on the right track with how you tried to fix this. With v2.6.0 of the Lifecycle library, the Transformations was completely removed (which you unfortunately had to find out the hard way), but thankfully the code is simpler now.

For a concrete example, this code:

this.currentPlayer = Transformations.map(this.currentGame) { gameWithPlayers ->
    gameWithPlayers?.players?.firstOrNull { it.isRolling }
}

is now

this.currentPlayer = this.currentGame.map { gameWithPlayers ->
    gameWithPlayers?.players?.firstOrNull { it.isRolling }
}

But, as you discovered, this causes issues with nullability due to how newer versions of LiveData handle null values. Luckily, this is an easy enough fix; change the LiveData variable to have a nullable type inside of it.
Specifically, this means:

val currentPlayer: LiveData<Player>

becomes

val currentPlayer: LiveData<Player?>

That should resolve the issues you have, let you move forward, and get rid of the Transformations class which was never my favorite in the first place!

Well, that seems to have done the trick as far as Transformations is concerned! Thanks!

Unfortunately, I’m now getting errors at runtime, and the app instantly crashes. Apparently, they are related to the database. I first got these errors when I ran the app after making changes up to p. 136 (right before the point where I should be able to use the database inspector to view the database). Clearly, there’s something wrong with how I’ve done the database, but I haven’t been able to find it yet. Here is the Logcat output I’m getting:

---------------------------- PROCESS STARTED (17921) for package com.example.pennydrop4 ----------------------------
2023-09-07 22:41:36.331 17921-17921 LoadedApk com.example.pennydrop4 D LoadedApk::makeApplication() appContext=android.app.ContextImpl@e2288d6 appContext.mOpPackageName=com.example.pennydrop4 appContext.mBasePackageName=com.example.pennydrop4 appContext.mPackageInfo=android.app.LoadedApk@5e9a757
2023-09-07 22:41:36.331 17921-17921 NetworkSecurityConfig com.example.pennydrop4 D No Network Security Config specified, using platform default
2023-09-07 22:41:36.356 17921-17921 NetworkSecurityConfig com.example.pennydrop4 D No Network Security Config specified, using platform default
2023-09-07 22:41:36.388 17921-17921 ActivityThread com.example.pennydrop4 D handleBindApplication() – skipGraphicsSupport=false
2023-09-07 22:41:36.509 17921-17921 AppCompatDelegate com.example.pennydrop4 D Checking for metadata for AppLocalesMetadataHolderService : Service not found
2023-09-07 22:41:36.571 17921-17921 DecorView com.example.pennydrop4 I [INFO] isPopOver=false, config=true
2023-09-07 22:41:36.571 17921-17921 DecorView com.example.pennydrop4 I updateCaptionType >> DecorView@edff65b[], isFloating=false, isApplication=true, hasWindowDecorCaption=false, hasWindowControllerCallback=true
2023-09-07 22:41:36.572 17921-17921 DecorView com.example.pennydrop4 D setCaptionType = 0, this = DecorView@edff65b[]
2023-09-07 22:41:36.617 17921-17921 mple.pennydrop com.example.pennydrop4 W Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (greylist, reflection, allowed)
2023-09-07 22:41:36.619 17921-17921 mple.pennydrop com.example.pennydrop4 W Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (greylist, reflection, allowed)
2023-09-07 22:41:37.223 17921-17921 AndroidRuntime com.example.pennydrop4 D Shutting down VM
2023-09-07 22:41:37.229 17921-17921 AndroidRuntime com.example.pennydrop4 E FATAL EXCEPTION: main
Process: com.example.pennydrop4, PID: 17921
java.lang.RuntimeException: Cannot create an instance of class com.example.pennydrop4.viewmodels.GameViewModel
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:322)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:306)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:280)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
at com.example.pennydrop4.fragments.GameFragment.getGameViewModel(GameFragment.kt:15)
at com.example.pennydrop4.fragments.GameFragment.onCreateView(GameFragment.kt:24)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3114)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:557)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2945)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3148)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:588)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2952)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:350)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
at android.app.Activity.performStart(Activity.java:8231)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3872)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:314)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:306)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:280)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
at com.example.pennydrop4.fragments.GameFragment.getGameViewModel(GameFragment.kt:15)
at com.example.pennydrop4.fragments.GameFragment.onCreateView(GameFragment.kt:24)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3114)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:557)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2945)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3148)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:588)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2952)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:350)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
at android.app.Activity.performStart(Activity.java:8231)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3872)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.RuntimeException: Cannot find implementation for com.example.pennydrop4.data.PennyDropDatabase. PennyDropDatabase_Impl does not exist
2023-09-07 22:41:37.229 17921-17921 AndroidRuntime com.example.pennydrop4 E at androidx.room.Room.getGeneratedImplementation(Room.kt:58)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.kt:1357)
at com.example.pennydrop4.data.PennyDropDatabase$Companion.getDatabase(PennyDropDatabase.kt:46)
at com.example.pennydrop4.viewmodels.GameViewModel.(GameViewModel.kt:29)
… 43 more
2023-09-07 22:41:37.313 17921-17921 Process com.example.pennydrop4 I Sending signal. PID: 17921 SIG: 9
---------------------------- PROCESS ENDED (17921) for package com.example.pennydrop4 ----------------------------
---------------------------- PROCESS STARTED (17984) for package com.example.pennydrop4 ----------------------------
2023-09-07 22:41:40.399 17984-17984 LoadedApk com.example.pennydrop4 D LoadedApk::makeApplication() appContext=android.app.ContextImpl@e2288d6 appContext.mOpPackageName=com.example.pennydrop4 appContext.mBasePackageName=com.example.pennydrop4 appContext.mPackageInfo=android.app.LoadedApk@5e9a757
2023-09-07 22:41:40.400 17984-17984 NetworkSecurityConfig com.example.pennydrop4 D No Network Security Config specified, using platform default
2023-09-07 22:41:40.463 17984-17984 NetworkSecurityConfig com.example.pennydrop4 D No Network Security Config specified, using platform default
2023-09-07 22:41:40.539 17984-17984 ActivityThread com.example.pennydrop4 D handleBindApplication() – skipGraphicsSupport=false
2023-09-07 22:41:40.942 17984-18174 System com.example.pennydrop4 W A resource failed to call close.
2023-09-07 22:41:40.942 17984-18174 System com.example.pennydrop4 W A resource failed to call close.
2023-09-07 22:41:45.658 17984-18187 ProfileInstaller com.example.pennydrop4 D Installing profile for com.example.pennydrop4
2023-09-07 22:41:45.668 17984-18187 mple.pennydrop com.example.pennydrop4 E Failed to open file ‘/data/data/com.example.pennydrop4/code_cache/.overlay/base.apk/assets/dexopt/baseline.prof’: No such file or directory

There are a few things you can check here:

  • Make sure you have the kotlin-kapt plugin in your plugins block under the app’s build.gradle file:

    plugins {
        // Other plugins are still here
    
        id 'kotlin-kapt'
    }
    
  • Make sure the Room compiler KAPT dependency in also in the app’s build.gradle file:

    dependencies {
      // Other dependencies are still here
    
      kapt "androidx.room:room-compiler:$room_version"
    }
    
  • Make sure your PennyDropDatabase class has the @Database annotation:

      @Database(
          entities = [Game::class, Player::class, GameStatus::class],
          version = 1,
          exportSchema = false
      )
      @TypeConverters(Converters::class)
      abstract class PennyDropDatabase : RoomDatabase() {
        // Database code is in here
      }
    

If it’s still not working after all those pieces are in place, let me know! Also, if you have your code in GitHub somewhere, that’ll make troubleshooting easier since I can see all the pieces.

Well, the AndroidStudio IDE apparently “helpfully” had me change one of those KAPT entries due to KAPT being the “old” way of doing things, and the “new” way wasn’t working! I changed them back, and it runs, with the game playing fine! Thanks!

The only remaining problem is that I now see that GameViewModel has two “when branch is never reachable” errors, under “val statuses = …” in the function updateFromGameHandler. I can’t see any reason why they’d never be reachable at the moment, but perhaps I’ll figure it out when I next look at the code.

If you’d like to take a look, I have made my code public on GitHub: GitHub - dachristenson/PennyDrop4: Fourth attempt at working through "Kotlin and Android Development featuring Jetpack" due to changes in Android Studio.

Thanks again!

Sorry that I missed this before! I answered in this post, but I’ll note something similar here as well in case anyone else runs into this.

My take is that the unreachable code “error” is actually incorrect and just the linter getting confused by the nullability of previousPlayer and currentPlayer. If I put the same logic into a Kotlin command-line app, it works just fine, and if you refactor the when block to skip the subject, that also works fine.