Search code examples
androidkotlinandroid-gallery

How to change this function to get rid of Redundant SAM-constructior?


So, i want to make SetOnClickListener that will be responsible for opening gallery. It works, but when i try to add code to commit to github i can't, because i used Redundant SAM-Constructor. So my question is, how to change my code, so it will work without it?

class MainActivity : AppCompatActivity() {
private var dialogView: View? = null

private val getPreviewImage = registerForActivityResult(ActivityResultContracts.GetContent(), ActivityResultCallback {
    it?.let { uri ->
        dialogView?.findViewById<ImageView>(R.id.imageChange)?.setImageURI(it)
    }?:run {
        Log.e("MainActivity", "URI not present")
    }
})

private val getPreviewVideo = registerForActivityResult(ActivityResultContracts.GetContent(), ActivityResultCallback {
it?.let { uri ->
    dialogView?.findViewById<VideoView>(R.id.videoChange)?.setVideoURI(it)
}?: run{
    Log.e("MainActivity", "URI not present")
    }
})

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

    bottomNavigationView.background = null
    bottomNavigationView.menu.findItem(R.id.placeholder).isEnabled = false
    replaceFragment(HomeFragment())

    bottomNavigationView.setOnItemSelectedListener {
        when (it.itemId) {
            R.id.home -> replaceFragment(HomeFragment())
            R.id.player -> replaceFragment(PlayerFragment())
            R.id.profile -> replaceFragment(ProfileFragment())
            R.id.settings -> replaceFragment(SettingsFragment())
        }
        true
    }

    popupAddButton.setOnClickListener {
        showDialog()
    }
}

private fun replaceFragment(fragment: Fragment) {
    val transaction = supportFragmentManager.beginTransaction()
    transaction.replace(R.id.fragment_container, fragment)
    transaction.commit()
}
@SuppressLint("InflateParams")
private fun showDialog() { //this is for popupWindow
    dialogView = layoutInflater.inflate(R.layout.popup, null)
    val dialog = Dialog(this)
    val titleEditText = dialogView?.findViewById<EditText>(R.id.titleEdit) //popUp edit field title
    val descEditText = dialogView?.findViewById<EditText>(R.id.description) //popUp edit field description

    dialogView?.addImage?.setOnClickListener {
        getPreviewImage.launch("image/*")

    }
    dialogView?.addVideo?.setOnClickListener {
        getPreviewVideo.launch("video/*")
    }

    dialogView?.addButton?.setOnClickListener {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
        if (titleEditText?.text?.isEmpty() == true || descEditText?.text?.isEmpty() == true){
            Toast.makeText(applicationContext, "add required data", Toast.LENGTH_SHORT).show()
        }else{
            Toast.makeText(applicationContext, "Added", Toast.LENGTH_SHORT).show()
        }
    }
    dialog.setContentView(dialogView!!)
    dialog.show()
   }
}

Solution

  • I'm guessing that what you mean is that some lint rule for the Git repo you're trying to push to doesn't allow redundant SAM constructors.

    A SAM constructor is when you use a fake "constructor" supplied by Kotlin for functional interfaces. For any Java functional interface (SAM) (or Kotlin fun interface), Kotlin creates an implicit inline function that looks like a constructor. It uses the name of that interface as the function name and takes a parameter of a functional reference matching the signature of the SAM.

    So for example, if you define this interface in Java

    public interface Foo {
        public int bar(String value);
    }
    

    or if you defined it in Kotlin as

    fun interface Foo {
        fun bar(value: String): Int
    }
    

    Kotlin implicitly creates this function. You can't see it anywhere but you can use it. It's inline only so cannot be accessed in Java or by reflection.

    inline fun Foo(crossinline function: (value: String)->Int) = object: Foo {
        override fun bar(value: String): Int = function(value)
    }
    

    This fake constructor is for when you want to create an instance of that interface to store in a property. You don't need to use this SAM constructor when you are passing a lambda as the last argument of a function, because you can just pass the lambda directly without calling this constructor. Kotlin automatically knows what interface to build out of the lambda.*

    So your code like

    private val getPreviewImage = registerForActivityResult(ActivityResultContracts.GetContent(), ActivityResultCallback {
        it?.let { uri ->
            dialogView?.findViewById<ImageView>(R.id.imageChange)?.setImageURI(it)
        }?:run {
            Log.e("MainActivity", "URI not present")
        }
    })
    

    is using a redundant SAM constructor and can be replaced with

    private val getPreviewImage = registerForActivityResult(ActivityResultContracts.GetContent()) { 
        it?.let { uri ->
            dialogView?.findViewById<ImageView>(R.id.imageChange)?.setImageURI(it)
        }?:run {
            Log.e("MainActivity", "URI not present")
        }
    }
    

    You also used a redundant SAM constructor for val getPreviewVideo.

    * An exception is when there are overloads of a function that take different interfaces as the last parameter. Then you would need the fake constructor to distinguish between them for the constructor.


    Side note: it is bad practice to chain scope functions like that (?.let...?:run). Aside from being hard to read, it is very easy to accidentally do something that will cause both blocks to be executed. For example, if the URI is not null but dialogView is null, your run block will be executed anyway because let will evaluate to null. I would write it like this:

    private val getPreviewImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        if (uri != null) {
            dialogView?.findViewById<ImageView>(R.id.imageChange)?.setImageURI(uri)
        } else {
            Log.e("MainActivity", "URI not present")
        }
    }