Category: Coding

  • How to start using view binding //Android Kotlin Dev Diary

    First, open your app/build.gradle file

    Inside the android{} block of app/build.gradle, add the following:

    buildFeatures { viewBinding = true }

    Sync project with gradle files.

    In your activity or fragment, define the following class-wide property:

    private lateinit var binding: FragmentorActivityNameBinding

    Replace the FragmentorActivityNameBinding above with the appropriate binding class name. Tip: The binding class name is based on the XML file’s name. For example, if your XML file name is activity_main.xml, the binding class name should be ActivityMainBinding. Or, if your file name is fragment_main.xml, the binding class name should be FragmentMainBinding.

    In your activity, the onCreate function should look like this:

    override fun onCreate(savedInstanceState: Bundle?) {
    
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }

    Otherwise, if using view binding in a fragment, your onCreateView should look like this:

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
    
        binding = FragmentMainBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = mainViewModel
        return binding.root
    }
  • Disable animations programmatically during instrumented tests // Android Dev Diary

    When running an instrumented Android test, or “androidTest”, and you encounter the following error:

    androidx.test.espresso.PerformException: Error performing 'scroll to' on view 'Animations or transitions are enabled on the target device.

    It means that you need to disable animations for it to successfully run the test.

    But, it can take a lot of clicks on the emulator/device screen to get to the developer settings to turn off animations!

    So, for a quicker way to disable animations in the Android device during development, just enter the following commands in your Android Studio terminal:

    adb shell settings put global window_animation_scale 0.0
    adb shell settings put global transition_animation_scale 0.0
    adb shell settings put global animator_duration_scale 0.0
  • Exception: No signature of method android() is applicable for argument types, Build Gradle // Dev Diary

    Today I wanted to make a data class a Parcelable so that I could pass it as an argument in Navigation component.

    Parcelables, though better in performance than Serializables, take more time to implement! To help us out, there’s a “kotlin-parcelize” plugin that can generate the boilerplate code for us.

    There were issues though.

    After applying the kotlin-parcelize plugin, I was getting the error:

    'kotlin-parcelize' can't be applied together with 'kotlin-android-extensions'

    So kotlin-parcelize is like a “successor” to the now deprecated 'kotlin-android-extensions' that I’m still using. So I replaced 'kotlin-android-extensions' with ‘kotlin-parcelize’.

    I got another error after attempting to sync with my Gradle files:

    org.gradle.api.GradleScriptException: A problem occurred evaluating project ':app'
    [...]
    Caused by: groovy.lang.MissingMethodException: No signature of method: build_54gmhuqjm83le8li4fspjsxlu.android() is applicable for argument types: (build_54gmhuqjm83le8li4fspjsxlu$_run_closure1) values: [build_54gmhuqjm83le8li4fspjsxlu$_run_closure1@3c374eea]
    	at build_54gmhuqjm83le8li4fspjsxlu.run([...]\app\build.gradle:11)
    

    The solution was to remove the following code from my android{} block:

    androidExtensions {
        experimental = true
    }

    After removing that, Gradle synced successfull!

    References:
    Parcelable implementation generator
    How to use @Parcelize now that kotlin-android-extensions is being deprecated?

  • Two-way data binding with StateFlow & BindingAdapters //Dev Diary

    I created BindingAdapters for an EditText like so:

    @BindingAdapter("android:text")
    fun setText(editText: EditText, inputAmount: MutableStateFlow<Double>) {
        if (editText.text.toString() != inputAmount.value.toString()) {
            editText.setText(inputAmount.value.toString())
            editText.setSelection(editText.text.length)
        }
    }
    
    @InverseBindingAdapter(attribute = "android:text")
    fun getText(editText: EditText): Double {
        if (editText.text.isEmpty()) return 0.0
        return editText.text.toString().toDouble()
    }
    
    

    My XML layout element looks like this:

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/tiet_input_amount"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/input_hint_amount"
                    android:inputType="numberDecimal"
                    android:text="@={viewModel._inputAmount}" />

    And in my ViewModel, the inputAmount is set up this way:

        val _inputAmount: MutableStateFlow<Double> = MutableStateFlow(0.0)
        val inputAmount: StateFlow<Double> = _inputAmount
  • Fix coroutine error: Cannot access database on the main thread // Android Dev Diary

    As I was running a test for a ViewModel function, I encountered the following error:

    [...] Main Thread @coroutine java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

    I think it may be caused by the viewModelScope from viewModelScope.launch {} being used by the running test, which may not be accessible during unit tests.

    What I did to fix this error on my unit test was to enable main thread operations in my in-memory Room database test instance. I simply added .allowMainThreadQueries() while building the database instance like so:

    db = Room.inMemoryDatabaseBuilder(
        context, MyRoomDatabase::class.java)
        .allowMainThreadQueries()
        .build()

    Is that the best way to do it? If you got a better suggestion, please comment! I’d love to hear about it.

  • Grant, revoke permissions via Android Studio Terminal (ADB Shell) // Android Dev Diary

    To grant and revoke permissions via Terminal (ADB Shell), run your app on an emulator (I haven’t personally tried on a device but it should work there too).

    Open your Terminal, and then enter the following command:

    adb shell

    Once you’re in, you may use the following syntax for granting or revoking your Android device’s permissions:

    pm grant <package-name> <permission>

    For example, to grant a permission:

    pm grant net.catzie.myapp android.permission.READ_EXTERNAL_STORAGE

    pm grant net.catzie.myapp android.permission.WRITE_EXTERNAL_STORAGE

    And to revoke a permission:

    pm revoke net.catzie.myapp android.permission.READ_EXTERNAL_STORAGE

    pm revoke net.catzie.myapp android.permission.WRITE_EXTERNAL_STORAGE

    Now there’s no need to open the settings screen on your device to play with your Android permissions!

  • Remove room database on app uninstall // Android Dev Diary

    The problem:

    Installing on an Android device that has an old version of an app I’m developing triggers an error:

    Room Database Migration: java.lang.IllegalStateException: Migration didn't properly handle

    Problem persists despite uninstalling the Android app and deleting all app data.

    The solution:

    Very simple. I set allowBackup to false in my AndroidManifest.xml file like so:

    android:allowBackup="false"

    Then I reinstalled the app on the Android device. Error was still triggered on first install, but after one more round of uninstall and reinstall, the error finally went away!

    Side Note / Quick Life Update:

    I have been working on an app, with me as its first user. I tentatively call it “bodymonitor”. It should let me log foods I ate, meds/supplements I took, and body sensations. My brain does not do a good job of monitoring these things so I shall get an app to help me do it. There are existing apps out there that do more or less the same thing, but I want one that caters to my specific needs. Doesn’t hurt to practice Android app development as I make the app, too!

    This weekend I’ll undergo endoscopy again, but this time it’s called a “flexible nasopharyngolaryngoscopy” which will pass through the nose instead of the mouth, to be done under local anesthesia. From the sound of it I don’t expect to be put to sleep, which makes me less anxious, but still anxious. XD It’s gonna be done because of my throat issues.

  • Get Navigation Component destinations in back stack // Android Kotlin Dev Diary

    It’s simple to get the list of destinations that’s currently in your back stack. The only thing you need is a reference to the Navigation Component’s NavController.

    The NavController object has a backQueue property that you can inspect.

    backQueue is an array, and each item’s destination property is what you’re probably looking for!

    To show my current Navigation’s back stack, I use Android Studio’s Debugger, and create watches such as the following:

    Screenshot taking when no frames were available, and thus the texts are red…

    I haven’t literally tried, but logging them programmatically like so should work too:

    for(item in navController.backQueue){
        Log.d("MyNavigationBackStack", "item.destination.displayName: ${item.destination.displayName}")
    }
    Log.d("MyNavigationBackStack", "navController.backQueue: ${navController.backQueue}")
    Log.d("MyNavigationBackStack", "navController.currentBackStackEntry.destination.displayName: ${navController.currentBackStackEntry.destination.displayName}")
    Log.d("MyNavigationBackStack", "navController.currentDestination: ${navController.currentDestination}")

    Lemme know if these work, or if you have relevant tips that haven’t been mentioned!

  • A working shared ViewModel between parent and child Fragments //Android Kotlin Dev Diary

    I initially had a problem with the shared ViewModel because the child fragments were creating a new instance from the one held by the parent fragment.

    The ViewModel instantiations below work for me:

    CreateLogFragment.kt (parent)

    CreateLogFoodFragment.kt (child)

    CreateLogNotesFragment.kt (another child)

    by activityViewModels turned things around!

  • Two NavHosts with their own NavControllers and NavGraphs (Navigation Component) //Android Kotlin Dev Diary

    I am trying to make a single-activity app because I’d like to easily share data across screen using, ideally one, or as few as possible ViewModels.

    NavGraph 1

    Host: Layout file of MainActivity

    Destination(s): CreateLogFragment

    NavGraph 2

    Host: Layout file of CreateLogFragment

    Destination(s): CreateLogFoodFragment, CreateLogNotesFragment

    (CreateLogFragment has a BottomNavigationBar that switches between CreateLogFoodFragment and CreateLogNotesFragment)

    The problem was that NavGraph 2 was not getting the correct NavHost and NavController in the CreateLogFragment class.

    And the solution is to retrieve the NavHost in CreateLogFragment like so:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

        val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_create_log) as NavHostFragment
        val navController = navHostFragment.navController
    
        // Setup Bottom Nav
        val bottomNav: BottomNavigationView = binding.bottomNav
        bottomNav.setupWithNavController(navController)
    }

    I’d like to write a full tutorial on how to do this but I’m not sure if I could spare some time. If you’ve been reading my recent posts you’ll notice that I’m not able to put it so much effort in explaining everything. These days I’m always in a hurry to finish stuff. But I’ll still try!!