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/