free geoip
34

Kotlin App Works on Emulator But Not Device

When developing Android applications using Kotlin in Android Studio, many developers encounter a frustrating issue: the app works perfectly fine…

When developing Android applications using Kotlin in Android Studio, many developers encounter a frustrating issue: the app works perfectly fine on the emulator but fails or behaves unexpectedly on a real device. This is a common problem that can occur due to multiple reasons, ranging from API compatibility, permissions, hardware limitations, or even differences in device configuration. In this article, we will explore the most common causes, real-world examples, and provide practical code fixes to ensure your Kotlin app runs smoothly on both emulator and physical Android devices.

Kotlin App Works on Emulator But Not Device

1. Common Causes of Emulator vs. Real Device Issues

  • Missing Permissions: The emulator often grants permissions automatically, but on a real device, users must explicitly allow them.
  • Different API Levels: Your emulator might be running the latest Android version, while your physical device uses an older API.
  • Hardware Dependencies: Features like camera, GPS, Bluetooth, and sensors behave differently on real devices.
  • ProGuard or R8 Issues: Code obfuscation during release builds may break certain classes or methods.
  • File Path Differences: The emulator may have different file structures compared to physical devices.

2. Example Case: Permission Issue

One of the most common issues is related to runtime permissions. Suppose you are building a simple Kotlin app that accesses the camera. On the emulator, it works without problems, but on a real device, the app crashes because the camera permission is not granted properly.

class MainActivity : AppCompatActivity() {

    private val CAMERA_REQUEST_CODE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Check and request permission
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, 
                arrayOf(Manifest.permission.CAMERA), CAMERA_REQUEST_CODE)
        } else {
            openCamera()
        }
    }

    private fun openCamera() {
        val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_REQUEST_CODE && grantResults.isNotEmpty() && 
            grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            openCamera()
        } else {
            Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show()
        }
    }
}

On the emulator, permissions may be granted by default, but on a real device, users must allow it manually. This code ensures the app checks and requests permission before accessing the camera.

3. Example Case: API Level Compatibility

Another issue happens when you use APIs not supported by your physical device. For example, suppose you are using the BiometricPrompt API which requires Android 9 (API 28) or higher. If your real device runs Android 7, the app will crash.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    val biometricPrompt = BiometricPrompt.Builder(this)
        .setTitle("Login")
        .setSubtitle("Authenticate with biometrics")
        .setNegativeButton("Cancel", mainExecutor, { _, _ -> })
        .build()

    biometricPrompt.authenticate(CancellationSignal(), mainExecutor,
        object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult?
            ) {
                super.onAuthenticationSucceeded(result)
                Toast.makeText(applicationContext, "Success!", Toast.LENGTH_SHORT).show()
            }
        })
} else {
    Toast.makeText(this, "Biometric not supported on this device", Toast.LENGTH_LONG).show()
}

This code ensures backward compatibility by checking the API version before calling features only available in newer Android versions.

4. Debugging Techniques

  • Use Logcat: Always check Logcat output when running on a real device to capture specific error messages.
  • Check Permissions in Settings: Ensure permissions are manually granted on the physical device.
  • Enable Developer Options: Sometimes USB debugging restrictions affect app behavior.
  • Test on Multiple Devices: Don’t rely only on one emulator or device for testing.

5. ProGuard / R8 Issues

If your app works in debug mode but crashes on the release build, ProGuard (or R8) may be removing required classes. Add rules to your proguard-rules.pro file:

# Keep all classes extending AppCompatActivity
-keep class * extends androidx.appcompat.app.AppCompatActivity { *; }

# Keep all model classes (example)
-keep class com.example.myapp.models.** { *; }

# Keep Retrofit/Gson models
-keep class com.google.gson.** { *; }
-keep class retrofit2.** { *; }

This ensures important classes are not stripped during minification.

6. Final Thoughts

The difference between emulator and real device performance is a challenge every Android developer faces. By carefully handling permissions, ensuring API compatibility, writing defensive code, and checking ProGuard rules, you can make sure your Kotlin app works on both emulator and physical devices without issues. Always remember to test on multiple real devices to catch edge cases that the emulator may not reveal.

rysasahrial

Leave a Reply

Your email address will not be published. Required fields are marked *