Ne demek startActivityForResult() is deprecated ?

Android, 2007'de ortaya çıktığından beri, Activity temel bileşenlerinden biri olmuştur. Bu bileşenler arasında veri taşımadaki kahramanlarımız Intent ve onActivityResult oldu şu ana kadar. Geçtiğimiz günlerde startActivityForResult() üzerinde bir çizgi gördük DEPRECATED  hadi bakalım  ⚡


Kas 01, 2021
hamurcuabi

1
619

Geleneksel Yöntem

Galeriden resim seçmek, izin istemek ve uygulamanın diğer ekranlarında kendi özel verilerimizi taşımak gibi durumlarda tüm sonuçlar ActivityResult() üzerinde işlenir. Bu gibi durumlarda kod bloklarımız aşağıdaki gibi bir karmaşık bir hal alabiliyor.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        //  Capture Image with Camera
        if (requestCode == CAMERA_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                val imageBitmap = data.getExtras().get("data") as Bitmap
                // Do Something 
            } else {
                // Couldn't take camera image
            }
        }

        // Pick from Gallery
        else if (requestCode == GALLERY_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                // Do Something
            }
            else {
                // Couldn't pick image from Gallery.
            }
        }

        // Custom Activity Result
        else if (requestCode == MY_OTHER_ACTIVITY_REQUEST && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                var myValue = data.getStringExtra("myValueKey")
                // Do something 
            }
            else {
                // Maybe user cancelled it.
            }
        }
    }

Nasıl sorunlar yaşandı şimdiye kadar ?

Tight Coupling: Her bir Capture(görüntü alma) veya Pick image(Resim seçme) vb. durumlarını ayırmak istersek bunu yapamayız. Daha sonra her biri için ayrı ayrı func(fonksiyon) yazarak biraz daha geliştirebiliriz ancak başlangıç noktası her zaman burası olacaktır. Bu durumda , iç içe geçmiş if-else blokları ile yukarıdaki kod gibi okunması zor kod blokları oluşacaktır.

Type-Safety: Bize dönecek olan data’nın tipini sadece biz biliyoruz ve bütün durumları kendimiz yönetiyoruz. String beklediğimiz bir yerden Integer gelirebilir ve bu sorun için sürekli debug yapmak gerekebilir. Bazı durumlarda NPE(NullPointerException) hatalarına bile yol açabilir.

Bu gibi nedenlerle, Google bize kod okunabilirliği açısından daha temiz bir çözüm getiriyor ve NPE durumunu tamamen ortadan kaldıryor bu nedenle içerde yazılaması muhtemel Try Catch bloklarınızı silmeye hazır olun!

Yeni yöntem (Activity Results Contracts)

Her şeyden önce gerekli olan dependecies’leri ekleyerek başlayalım. Bu yazıyı okuduğunuz tarih itibari ile versiyon numaraları değişmiş olabilir bunlara da dikkat etmenizi tavsiye ederim.

implementation 'androidx.activity:activity-ktx:1.3.0-alpha07'
implementation 'androidx.fragment:fragment-ktx:1.3.3'

ActivityResultContracts<I:O> class’ındaki generic alanlardan I — > input, O — >Output’u temsil etmektedir. Bu class’dan extend edilmiş olan class’lar createIntent() ve parseResult() fonksiyonlarını override ederler. createIntent() , yazılan contract çağrıldıktan sonra startActivityForResult() yönteminde geçirilecek, geçerli Intent’i oluşturmak için kullanılır. parseResult() , onActivityResult() yöntemi için bir proxy görevi görür ve sonuç Intent’ini ayrıştırıp verileri çıkarır.

Örneğin permission işlemi için hazırlanmış olan(pre-build) ;

ActivityResultContracts.RequestPermission<String,Boolean>

String bir input alırken bizlere Boolean return eder ve bu sayede android api level durumuna bakmamıza ya da onRequestPermissionResult’ı override edip içerde ekstra işler yapmamıza gerek kalmıcak. Aşağıda bazı pre-build contractların listesi mevcut.

  • StartActivityForResult()
  • RequestMultiplePermissions()
  • RequestPermission()
  • TakePicturePreview()
  • TakePicture()
  • TakeVideo()
  • PickContact()
  • CreateDocument()
  • OpenDocumentTree()
  • OpenMultipleDocuments()
  • OpenDocument()
  • GetMultipleContents()
  • GetContent()

1. Create Contract

Kendimiz oluşturabileceğimiz gibi hazır olarak varolan bir ActivityResultContracts ile başlayabiliriz. Basitten başlamak adına ActivityResultContracts.RequestPermission ile bir örnek yapalım.

2. Register Contract

Bu adım da registerForActivityResult() yöntemini çağıracağız.

private val requestPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
    Log.d("requestPermission", granted)
}

3. Call Contract

Bu adımda yapmamız gereken tek şey oluşturduğumuz contract’ı launch diyerek çalıştırmak.

requestPermission.launch(Manifest.permission.CAMERA)

Fakettiyseniz Request code(cons val REQUEST_CAMERA) yok! Kocaman if-else blokları yok ! , NPE olma durumu yok. Multiple permission’lar için özel kütüphanalere gerek yok !

Activity Results Contracts ile onActivityResult() yöntemini çok temiz ve yeniden kullanılabilir bir yapıda kullanmanıza izin veren güzel bir soyutlamaya sahibiz. Bir güzel örnek daha yapalım ,aşağıdaki kod bloğunda mime type ile filtereleme yaparak galeriden içerik almanız bu kadar basit. Media Intent felan falan yok 😎

 // Register pre-build Contract
    private val pickImage =
        registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
            uri?.let { uri ->
                binding.imgCameraPic.setImageURI(uri)
            
            }
        }
    // Call contract
    pickImages.launch("image/*")

4. Create Custom Contract

Örneğimizde User nesnesini input olarak alan ve bize String? return eden bir contract oluşturup SecondActivity’e gönderip geri dönüş alacağız.

//Custom bir contract oluşturalım
const val KEY_USER_OBJECT = "KEY_USER_OBJECT"
const val KEY_RESULT = "RESULT"

@Parcelize
data class User(val name: String, val age: Int) : Parcelable

class SimpleContract : ActivityResultContract<User, String?>() {

    override fun createIntent(context: Context, input: User): Intent {
        val intent = Intent(context, SecondActivity::class.java)
        intent.putExtra(KEY_USER_OBJECT, input)
        return intent
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? = when {
        resultCode != Activity.RESULT_OK -> null
        else -> intent?.getStringExtra(KEY_RESULT)
    }
}

// MainActivity içinde register edelim
private val customContract = registerForActivityResult(SimpleContract()) { data ->
    Toast.makeText(this, "Data: $data", Toast.LENGTH_SHORT).show()
}

//MainActivity içinde bir buton click işleminde vb. launch edelim
customContract.launch(User("Hamurcu",26))

//SecondActivity'de gelen nesneyi alabiliriz.
   val user = intent.getParcelableExtra<User>(KEY_USER_OBJECT)

// SecondActivity'deki işimiz bitince setResult ile geri dönecek veriyi de ekleyerek finish() diyebiliriz.
 private fun setResult() {
        val intent = Intent()
        intent.putExtra(KEY_RESULT, "Başarılııı!")
        setResult(RESULT_OK, intent)
        finish()
    }

Projenin kaynak kodlarına gist üzerinden ya da github sayfamdan ulaşabilirsiniz. Umarım yararlı bir yazı olmuştur mutlu günler dilerim.

android kotlin startActivityForResult

Benzer Yazılar


Yorumlar


Kasım 05, 202112:20 @Burakcan

Gayet anlaşılır bir makale çok teşekkürler :)