2

I found two similar codes:

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)
binding.playButton.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment)
}

Java code from android view class:

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

The question is: how can I create such function where I can use trailing lambda or interface as parameter? I get type mismatch.

    interface One {
        fun a(): Int
    }

    class OneImp : One {
        override fun a(): Int {
            return 4
        }
    }

    fun test(one: One) {
        val a = one
    }

   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
       val a = OneImp()
       test (a)   //works fine
       test {
            a //error
       }
   }

Error:

Type mismatch.
Required:
TitleFragment.One
Found:
() → TitleFragment.OneImp

UPDATE:

After the answer of @Jenea Vranceanu, I have found my error in testing SAM (I used interface from kotlin file, while all the code should be in java). Solution will be: (before kotlinv v1.4 releases) create a java file:

public class Mine {
    public interface One {
        int a();
    }

    public class OneImpl implements One {
        @Override
        public int a() {
            return 4;
        }
    }

    public void test(One one) {}
}

Then I can use both function argument and lambda. In kotlin file now:

 Mine().test {4}
 val b = Mine().OneImpl()
 Mine().test (b)

PS. If he adds it to his answer I will delete if from here.

vitalii
  • 443
  • 6
  • 10
  • i have updated your question title to make it a question, feel free to edit it if you're not happy with my changes :) – a_local_nobody Jul 31 '20 at 11:32
  • In simple words `test { ... }` is same as `test({ ... })` The lambda is passed as parameter instead `One`. See [trailing lambda can be put outside of parentheses](https://kotlinlang.org/docs/reference/lambdas.html#passing-a-lambda-to-the-last-parameter). – Animesh Sahu Jul 31 '20 at 13:57
  • SAM is confusing to understand... :(( – vitalii Jul 31 '20 at 15:51

1 Answers1

2

You have misunderstood a little bit how binding.playButton.setOnClickListener worked in each case.

In the first one, Navigation component creates View.OnClickListener that is passed into setOnClickListener (notice parentheses or round brackets):

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

In Java it would look almost the same:

binding.getPlayButton().setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

The second example works a little bit differently. First, you create View.OnClickListener by using braces and you define the body of this View.OnClickListener method onClick(View view):

binding.playButton.setOnClickListener { /* empty body of onClick(View view) */ }

You have reference to the clicked view inside of braces:

binding.playButton.setOnClickListener { 
    it.context // `it` is the clicked view
}

// It is the same as
binding.playButton.setOnClickListener { view ->
    view.context
}

Why doesn't it work exactly?

This type of defining anonymous classes is possible only with SAM classes defined in Java and View.OnClickListener is a SAM class defined in Java. Kotlin SAM classes/interfaces do not support this feature yet.

By writing:

val a = OneImp()
test {
    a //error
}

You assume that test function declaration looks like this:

fun test(one: () -> One) {
    val a = one()
}

Note that in the second example you have at the top of your question Navigation.findNavController(it).navigate does not create View.OnClickListener object. The braces create this object and as this interface has only one abstract method there is no use to write this method signature so all the content declared inside of braces goes directly into void onClick(View view) method.

Update (official documentation)

SAM conversions in Kotlin. This is the official documentation and at the bottom of it is written:

... note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.

So it will never be supported for Kotlin interfaces as there is no need for it. As it appears I was wrong about "SAM will never be supported". It will be but it is not yet available. SAM conversions for Kotlin classes will be available starting from Kotlin 1.4 which is now a release candidate.

Update with a solution

The alternative solution was not added initially. Questioner politely requested to add the next code to make the answer complete. Thanks!

public class Mine { 
    public interface One { 
        int a(); 
    } 

    public class OneImpl implements One { 
        @Override public int a() { return 4; } 
    } 
    
    public void test(One one) {} 
}
Jenea Vranceanu
  • 4,530
  • 2
  • 18
  • 34
  • 1
    SAM conversion for Kotlin interfaces is supported in Kotlin 1.4, which is now at the release candidate stage. – Tenfour04 Jul 31 '20 at 12:12
  • Great! Thanks for pointing that out. I'll update my answer. – Jenea Vranceanu Jul 31 '20 at 12:23
  • @JeneaVranceanu, Thanks for the response, even though you didn't answer on question how to do. I tied to do it in java class, but I used interface from kotlin file that was my problem. So the answer will be to create new .java file with code ``` public class Mine { public interface One { int a(); } public class OneImpl implements One { @Override public int a() { return 4; } } public void test(One one) {} } ``` After this, IDE provides lambda hint. Could you please put it into you answer to make it full. – vitalii Jul 31 '20 at 15:34