Androidde Kullanıcı İzinleri
Androidde kullanıcı izinleri ilk bakışta çok karışık gözükmektedir. Zamanla alışacağız. :)
Not: Kullanıcı izinleri anlatımında kullanılan kodların tamamı bir YemekTarifi Projesinin kodlarıdır.
-
Kullanıcı izinleri gizlilik ve veri güvenliği açısından son derfece önemlidir.
-
Kullanıcı izinleri runtime'da istenir, böylece uygulama bu özelliği kullanmak istediğinde sorularak gereksiz isteklerden sakınılır.
İlk olarak uygulamanın hangi izinleri isteyeceğiniz belirtmemiz gerekiyor, Manifest dosyasında.
Manifest ve Binding İşlemleri
Biz bu örnekte storage ve media izinlerini istiyoruz.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"></uses-permission>
Sonrasında binding işlemini yapıyoruz.
private var _binding : FragmentTarifBinding?= null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTarifBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // Bellek sızıntılarını önlemek için binding temizlenir.
}
Şimdi launcher ve seçilen görsel ile ilgili tanımlamalarını yapıyoruz.
private lateinit var permissionLauncher : ActivityResultLauncher<String>
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
//------
private var secilenGorsel : Uri?= null
private var secilenBitmap : Bitmap?= null
-
permissionLauncher: Kullanıcıdan galeriye erişim izni istemek için kullanılır.
-
activityResultLauncher: Kullanıcı bir görsel seçtiğinde, bu sonucu almak için kullanılır.
-
secilenGorsel: Kullanıcının seçtiği resmin galeri içindeki yolunu (URI) tutar. URL gibi düşünülebilir.
-
secilenBitmap: Kullanıcının seçtiği resmin bitmap formatını tutar. Yani görsel formatını tutar.
Şimdi onViewCreated fonksiyonunda butonların onClick özelliklerini bağlayıp, fragmentten gelen argümanın durumuna göre butonların aktifliklerini ayarlayıyoruz.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.imageView.setOnClickListener { gorselSec(it) }
binding.silButton.setOnClickListener { sil(it) }
binding.kaydetButton.setOnClickListener { kaydet(it) }
arguments?.let {
val bilgi = TarifFragmentArgs.fromBundle(it).bilgi
if (bilgi == "yeniMi?") {
binding.silButton.isEnabled = false
binding.kaydetButton.isEnabled = true
} else {
binding.silButton.isEnabled = true
binding.kaydetButton.isEnabled = false
}
}
}

- Görüleceği üzere henüz silinecek bir görsel olmadığı için sil butonu inaktif durumdadır.
Android 13 ve Üzeri İçin
Şimdi kullanıcıdan galeriye ve dahili depolamaya erişim izni istemek için kod yazıyoruz. fun gorselSec (view: View)
fonksiyonunun içerisinde
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
Bu kod ile Android 13 ve üzeri için (API 33 ve üstü) izin istemek için yazıyoruz, sonrasında
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {//Daha önceden izin alınmış mı kontrolü
kodu ile birlikte daha önceden izin alınmış mı kontrolü yapıyoruz.
-
ContextCompat.checkSelfPermission metodu, ilgili iznin READ_MEDIA_IMAGES durumunu döndürür.
-
Eğer izin verilmişse direkt galeri açılır.
-
Eğer izin verilmemişse, != PackageManager.PERMISSION_GRANTED döner ve sonraki adımlara geçilir
Sonraki adımda
if(ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(),Manifest.permission.READ_MEDIA_IMAGES))
Snackbar.make(view, "Yemek görseli eklemek için galeri erişimi gerekmektedir.",Snackbar.LENGTH_INDEFINITE).setAction(
"İzinver",View.OnClickListener {
permissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
}).show()
-
shouldShowRequestPermissionRationale metodu, kullanıcının daha önce izin verip vermediğini kontrol eder.
-
Eğer kullanıcı daha önce izin vermemişse true döner ve izin istemeden önce bir açıklama yapılır.
-
Snackbar ile kullanıcıdan neden izin istediğimizi tekrar kullanıcıya söylüyoruz.
-
Snackbar.LENGTH_INDEFINITE, long veya short gibi süreli değil.

- Eğer kullanıcı daha önce izin ekranını hiç görmediyse false döner ve bir sonraki maddedeki kod ile direkt izin istenir.
else {
permissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
}
- Eğer kullanıcı daha önce izin ekranını hiç görmemişse veya direkt izin istememiz gerekiyorsa burada izin istemeye başlıyoruz.

- Eğer kullanıcı izni zaten vermişse
else {
val intentToGalery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
}
-
Eğer kullanıcı izni zaten vermişse direkt galeriye gitmek için intent başlatılır.
-
Intent.ACTION_PICK, kullanıcının galerisinden bir görsel seçmesini sağlar.
-
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, galeri içindeki görsellere erişmek için kullanılır.
-
activityResultLauncher.launch(intentToGalery), sonucu almak için galeriye yönlendirir.

Andorid 13 Altı İçin
-
Aynı izinleri andorid 13 altı için farklı bir şekilde yapmamız gerekiyor.
-
Andorid 13 altı için olan kısım öncekiyle aynı şekilde çalışıyor, tek fark Android 13 altındaki cihazlar için farklı bir izin (READ_EXTERNAL_STORAGE) kullanılıyor olması.
else{
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {//Daha önceden izin alınmış mı kontrolü
if(ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(),Manifest.permission.READ_EXTERNAL_STORAGE))
Snackbar.make(view, "Yemek görseli eklemek için galeri erişimi gerekmektedir.",Snackbar.LENGTH_INDEFINITE).setAction(
"İzinver",View.OnClickListener {
permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}).show()
else{
permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE
}
}
else {
val intentToGalery = Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
}
}
Diğer Kodlar
fun registerLauncher()
fonksiyonuna şunları yazıyoruz:
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){result ->
-
registerForActivityResult metodu, bir aktivite başlatıp sonucunu almak için kullanılır.
-
ActivityResultContracts.StartActivityForResult() kullanarak, galeriye gitme işlemini başlatacak bir etkinlik oluşturuyoruz.
-
{ result -> ... } kısmı, galeri dönüş verisini yakalamak için tanımlanmış bir lambda fonksiyonu.
if (result.resultCode == AppCompatActivity.RESULT_OK) {
val intentFromResult = result.data
-
result.resultCode == AppCompatActivity.RESULT_OK → Kullanıcının başarılı bir şekilde bir resim seçip seçmediğini kontrol eder.
-
result.data → Seçilen resmin verilerini içeren Intent nesnesini döndürür.
Seçilen resmi işleme ve gösterme işlemleri şu şekilde yapılır:
if (intentFromResult != null) {
secilenGorsel = intentFromResult.data // Bu bize verinin nerde kayıtlı olduğunu gösterir
try {
// SDK SÜRÜMÜ 28 VE ÜSTÜYSE ŞU, DEĞİLSE ŞU KONTROLÜ SAĞLA
if (Build.VERSION.SDK_INT >= 28) {
val source = ImageDecoder.createSource(
requireActivity().contentResolver,
secilenGorsel!!
)
secilenBitmap = ImageDecoder.decodeBitmap(source)
binding.imageView.setImageBitmap(secilenBitmap)
} else {
secilenBitmap = MediaStore.Images.Media.getBitmap(
requireActivity().contentResolver,
secilenGorsel
)
binding.imageView.setImageBitmap(secilenBitmap)
}
} catch (e: IOException) {
println(e.localizedMessage)
}
}
-
intentFromResult != null → Eğer Intent içeriği boş değilse (null değilse), kullanıcının gerçekten bir resim seçtiğini gösterir.
-
secilenGorsel = intentFromResult.data → Seçilen resmin URI’sini (content:// ile başlayan bir adres) alır.
Android 9 ve üzeri için ImageDecoder.createSource() ile bir görüntü kaynağı oluşturulur, ImageDecoder.decodeBitmap() ile bu kaynak bir Bitmap olarak okunur.
Andorid 9'dan eski sürümler için MediaStore.Images.Media.getBitmap() kullanılarak doğrudan Bitmap elde edilir.
try-catch bloğunda Eğer URI geçersizse veya dosya okunamazsa, IOException fırlatılır ve hata mesajı konsola yazdırılır.
Son olarak da permissionLauncher değişkeninin tanımlanması gerekiyor.
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()){result ->
-
Bu satır, kullanıcının bir izni verip vermediğini kontrol eden bir launcher (başlatıcı) kaydeder. Burada kullanılan ActivityResultContracts.RequestPermission() ile bir izin talebi yapılır.
-
İzin talebi sonucu alındığında, result parametresi true (izin verildi) ya da false (izin verilmedi) değerini döner.
Sonrasında
if (result){
val intentToGalery = Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
}else {
Toast.makeText(requireContext(),"İzin verilmedi",Toast.LENGTH_LONG).show()
}
} //ilk satırın süslü parantezi
-
Eğer izin verilirse galeriye gitmek için intent başlatılır.
-
Eğer izin verilmezse Toast mesajı görüntülenir.
Son olarak tanımladığımız bu registerLauncher fonksiyonunu onCreate fonksiyonunda çağırmamız gerekiyor.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerLauncher()
}
Anlattığımız her şeyin tek parça halindeki kodu:
package com.example.yemektarifleri
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.yemektarifleri.databinding.FragmentTarifBinding
import com.google.android.material.snackbar.Snackbar
import java.io.IOException
class TarifFragment : Fragment() {
private var _binding: FragmentTarifBinding? = null
private val binding get() = _binding!!
private lateinit var permissionLauncher: ActivityResultLauncher<String>
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
private var secilenGorsel: Uri? = null
private var secilenBitmap: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerLauncher()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTarifBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.imageView.setOnClickListener { gorselSec(it) }
binding.silButton.setOnClickListener { sil(it) }
binding.kaydetButton.setOnClickListener { kaydet(it) }
arguments?.let {
val bilgi = TarifFragmentArgs.fromBundle(it).bilgi
if (bilgi == "yeniMi?") {
binding.silButton.isEnabled = false
binding.kaydetButton.isEnabled = true
} else {
binding.silButton.isEnabled = true
binding.kaydetButton.isEnabled = false
}
}
}
fun kaydet(view: View) {}
fun sil(view: View) {}
fun gorselSec(view: View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.READ_MEDIA_IMAGES)) {
Snackbar.make(view, "Yemek görseli eklemek için galeri erişimi gerekmektedir.", Snackbar.LENGTH_INDEFINITE)
.setAction("İzin ver") { permissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES) }
.show()
} else {
permissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
}
} else {
val intentToGalery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
}
} else {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)) {
Snackbar.make(view, "Yemek görseli eklemek için galeri erişimi gerekmektedir.", Snackbar.LENGTH_INDEFINITE)
.setAction("İzin ver") { permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) }
.show()
} else {
permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
} else {
val intentToGalery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
}
}
}
fun registerLauncher() {
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == AppCompatActivity.RESULT_OK) {
val intentFromResult = result.data
intentFromResult?.let {
secilenGorsel = it.data
try {
if (Build.VERSION.SDK_INT >= 28) {
val source = ImageDecoder.createSource(requireActivity().contentResolver, secilenGorsel!!)
secilenBitmap = ImageDecoder.decodeBitmap(source)
binding.imageView.setImageBitmap(secilenBitmap)
} else {
secilenBitmap = MediaStore.Images.Media.getBitmap(requireActivity().contentResolver, secilenGorsel)
binding.imageView.setImageBitmap(secilenBitmap)
}
} catch (e: IOException) {
println(e.localizedMessage)
}
}
}
}
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
if (result) {
val intentToGalery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activityResultLauncher.launch(intentToGalery)
} else {
Toast.makeText(requireContext(), "İzin verilmedi", Toast.LENGTH_LONG).show()
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}

- Görüleceği üzere tüm bu işlemlerin sonunda galerideki bir fotoğrafı uygualamaya başarılı bir şekilde ekledik.
Androidde İzin Seviyeleri
- Android'de izin seviyeleri 3 farklı seviyeye ayrılır:
Normal İzinler
-
Normal izinler, kullanıcı gizliliği açısından tehlikeli olmayan izinlerdir. Bu izinler, genellikle cihazın temel işlevlerine erişim sağlar ve kullanıcıdan onay istenmez. Örneğin:
-
Bu izinler, uygulama yüklendiğinde otomatik olarak verilmiş sayılır ve Android işletim sistemi tarafından kullanıcıya gösterilmeden uygulanır.
Örnek olarak, internet erişimi verilebilir.
Tehlikeli (Dangerous) İzinler
-
Tehlikeli izinler, kullanıcının kişisel bilgilerine, cihazın donanımına veya diğer hassas verilere erişim sağlar.
-
Bu izinler, kullanıcının onayı gerektirir ve uygulama çalışırken izin isteme işlemi yapılır. Eğer kullanıcı izin verirse, uygulama ilgili kaynağa erişebilir.
-
Bu tür izinler için kullanıcıya bir açıklama gösterilmesi gerekebilir.
Örnek olarak, kamera erişimi, konum erişimi, dosya erişimi verilebilir.
Özel İzinler
- Android, bazı özel izinler için ek doğrulama gerektirebilir
Mesela, SYSTEM_ALERT_WINDOW, uygulamanın ekran üzerinde başka uygulamaların üstünde görünmesini sağlar (mesaj kutuları vb.).
İzinler ile İlgili Ek Notlar
-
Android 6.0 (API 23) ve sonrasında uygulama başlatıldığında izin istenmesi gerekir
-
İzinler, kullanıcıya zorla kabul ettirilmeye çalışılır yoksa kullanamaz ama yine de bilgilendirilmesi gerekir :)
-
Kullanıcı verdiği izinleri kaldırabilir.
-
Eğer kullanıcı izin istemini reddederse, uygulama genellikle bir rationale (açıklama) göstermelidir. Bu açıklama, kullanıcıya izin istemenin nedenini ve bu iznin uygulamanın düzgün çalışması için ne kadar gerekli olduğunu anlatır. Örneğimizde var.