2 February, 2022

Android Development Notes

Android Custom Views

Custom View Components https://developer.android.com/guide/topics/ui/custom-components

DI with Hilt

After creating a project, setup Hilt https://developer.android.com/training/dependency-injection/hilt-android

In project's build.gradle:

buildscript {
...
    ext.hilt_version = "2.28-alpha"

    dependencies {
      ...

        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

In app's build.gradle:

plugins {
  ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

...

dependencies {
  ...
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

Modules

https://developer.android.com/studio/projects/android-library

Persistence with Room

@MapInfo(valueColumn = "songCount")
@Query("""
       SELECT *, COUNT(mSongId) as songCount
       FROM Artist JOIN Song ON Artist.artistName = Song.artist
       GROUP BY artistName
       """)
fun getArtistAndSongCounts(): Map<Artist, Integer>

Theme and Styles

https://developer.android.com/guide/topics/ui/look-and-feel/themes https://medium.com/androiddevelopers/android-styling-themes-vs-styles-ebe05f917578

Configure build variants

https://developer.android.com/studio/build/build-variants

Anonymous Listener

Using SAM Single Abstract Method interfaces, creating implementation is realy easy with kotlin when you have an Java interface. Here I'm only interested in position value:

private val itemClickListener = OnItemClickListener { _, _, position, _ ->
  // do the onItemClick thing
}

But when you have a Kotlin interface it's getting verbose. The situtation is inconsistent, looks like Kotlin is still incomplete. Create an anonymoys object then override the single function:

@FunctionalInterface
interface OnPositiveClickListener {
  fun doPositiveClick(id: Long)
}

private val itemClickListener = object : OnPositiveClickListener {
  override fun doPositiveClick(id: Long) {
    // do the doPositiveClick thing
  }
}

For simplicity, use a workaround and declare the interface in Java instead of Kotlin:

// in .java file
public interface OnPositiveClickListener {
  void doPositiveClick(long id);
}

// in .kt kotlin
private val itemClickListener = OnPositiveClickListener {
  // do the doPositiveClick thing
}

Or don't use an inferface, use a functional interface instead:

fun interface OnPositiveClickListener {
  fun doPositiveClick(id: Long)
}

// in .kt kotlin
private val itemClickListener = OnPositiveClickListener {
  // do the doPositiveClick thing
}

Coroutine

Use suspend functions for long running work they can be called within a Coroutine.

suspend fun longRunning() = withContext(Dispatchers.IO) {

}

Block the current thread and start a new Coroutine. Use this in tests:

fun longRunning() = runBlocking {
  withContext(Dispatchers.Default) {

  }
}

Save File

I had (forced) to change the save-deck-activity for the latest update to use the system file picker instead. The official android documentation is still using deprecated startActivityForResult.

Create a starter:

  private var pickSaveFileStarter = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
      result.data?.also { onSaveFile(it) }
    }
  }

Start the pick-file process:

  private fun openPickSaveFileActivity(deckId: Long) {
    sourceDeckId = deckId
    pickSaveFileStarter.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
      addCategory(Intent.CATEGORY_OPENABLE)
      type = "application/tcd"
      putExtra(Intent.EXTRA_TITLE, "deck.tcd")

      // Optionally, specify a URI for the directory that should be opened in
      // the system file picker before your app creates the document.
      //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    })
  }

Call

  private fun onSaveFile(uri: Intent) {
    requireActivity().startService(Intent(activity, CardsService::class.java).apply {
      putExtra(EXTRA_TASK, TASK_STORE_WISE_CARDS_FILE)
      putExtra(EXTRA_DECK_ID, sourceDeckId)
      putExtra(EXTRA_FILE, uri)
    })
  }

Questions

How to inject dependencies into a Room migration. https://blog.termian.dev/posts/room-on-upgrade/