Tag: dev diary

  • Spoofing Battery and Forcing Sleep (Android Development)

    I wanted to do something with my Android phone using ADB commands, while keeping it plugged in to run the said commands. However, the thing I wanted to test was related to the idle (dozing or asleep) mode of Android devices. The workaround was to spoof the battery state to bypass Android’s charging restrictions.

    Spoofing Battery Charging Status and Forcing Android Device to Sleep (Idle Mode)

    • Turn off the device screen using the physical power button.
    • Trick Android into thinking the device is running on battery power: adb shell dumpsys battery unplug
    • Force the device to step into deep Doze mode: adb shell dumpsys deviceidle force-idle
    • Verify the device is successfully spoofed and asleep: adb shell dumpsys deviceidle get deep (This should return IDLE).

    Can also do:
    adb shell dumpsys battery unplug && adb shell dumpsys deviceidle force-idle && adb shell dumpsys deviceidle get deep

    Device Cleanup for Spoofed Battery and Forced Sleep (Crucial!)

    1. Once testing is done, you must tell the OS to start reading the actual battery state again, otherwise it won’t charge properly: adb shell dumpsys battery reset
    2. Bring the device out of idle: adb shell dumpsys deviceidle unforce

    Can also do:
    adb shell dumpsys battery reset && adb shell dumpsys deviceidle unforce && adb shell dumpsys deviceidle get deep

  • A random dev brain dump

    I don’t need a reason to randomly post a brain dump on my blog, no?

    • If an Android dev wishes to implement a precise “notification ID” that they want to be able to precisely cancel later (imagine that it plays an annoying alarm tone!) they can go about it either by
      • Using a unique ID tied to that notification’s base entity (e.g., an app’s incoming message) so that NOTIFS WILL STACK, or
      • Using a unique, dedicated ID that is defined as a constant and is defined without ties to the notification’s base entity so that ONLY THE LAST NOTIF WILL BE SHOWN
    • I gotta update my site’s, something..
    • If an Android dev’s soft keyboard covers a text input field when it’s displayed, particularly in Android 15 devices, they may wanna try to add an imePadding modifier to the root element (e.g., Box, Column) because this modifier will take care of spacings whenever the keyboard appears, avoiding issues such as covering the text input field.
  • Make a RecyclerView expand its height when new item is added //Android Dev Diary

    It seems that simply using android:layout_height="wrap_content" on your RecyclerView won’t make its height automatically expand when new items are added in it.

    (more…)
  • 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
    }
  • 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?

  • 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!