11

Background

I work on some large project on Android, that was originally written 100% in Java. As I've converted some files to Kotlin, I've found at least 2 cases that the documentation and the code declare that some parameters of some functions should be non-null, so I've let them stay this way on Kotlin too:

Meaning they look as such :

boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY);

The problem

I've noticed via Crashlytics that these 2 cases actually cause some devices to crash because of they are actually getting null values, as Kotlin is strict about such a thing (getting null when the parameter is set as non null).

What I've tried

I've reported about this on the issue tracker (here and here), and I tried to find how to force Kotlin to allow the parameters to be nullable, despite the code behind that says they are not. Sadly, it will always cause the error of "'onFling' overrides nothing", and won't let me build it.

I can't find how to do it, so for now, I use Java. Meaning something like this:

public class SimpleOnGestureListenerCompat extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onFling(@Nullable final MotionEvent e1, @Nullable final MotionEvent e2, final float velocityX, final float velocityY) {
        return super.onFling(e1, e2, velocityX, velocityY);
    }
}

I've also added Crashlytics to tell me if the second parameter can be null too, which is why I've set it to be nullable as well.

The questions

  1. Is this the only workaround I can use for now? Can't I use Kotlin somehow for this? Maybe use some annotation that removes the nullable check during runtime for new classes that I will make, that extend these problematic classes?
  2. Is this allowed only because Android Studio doesn't handle nullability so well/strict for Java, or is this expected to always be a possible workaround for Java? It didn't even show me a warning for using the wrong annotations
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Thanks for bringing this up. Do you want to share any update on your question? Have you found anything which solves this properly. I saw your comment on https://youtrack.jetbrains.com/issue/KT-53963 and looks like it is not solved – Shubham AgaRwal Jul 05 '23 at 04:26
  • Even Google/ Android team has marked as fixed https://issuetracker.google.com/issues/243267018#comment20 But didn't mention details about the same. – Shubham AgaRwal Jul 05 '23 at 04:27

3 Answers3

1

I have created my own SimpleOnGestureListener (named KotlinAdapterOnGestureListener) as a convenience class in Java, and then I can use Kotlin for my real OnGestureListeners extending KotlinAdapterOnGestureListener and overriding onMoveKt and onFlingKt instead of onMove and onFling:

public class KotlinAdapterOnGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    public boolean onSingleTapUp(@NonNull MotionEvent e) {
        return false;
    }

    public void onLongPress(@NonNull MotionEvent e) {
    }

    /** Instead of overwriting this method, overwrite onScrollKt, because despite
     * the annotation, the parameter e1 is sometimes null. */
    final public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2,
                            float distanceX, float distanceY) {
        return onScrollKt(e1, e2, distanceX, distanceY);
    }

    public boolean onScrollKt(MotionEvent e1, MotionEvent e2,
                            float distanceX, float distanceY) {
        return false;
    }

    /** Instead of overwriting this method, overwrite onFlingKt, because despite
     * the annotation, the parameter e1 is sometimes null. */
    final public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
                           float velocityY) {
        return onFlingKt(e1, e2, velocityX, velocityY);
    }

    public boolean onFlingKt(MotionEvent e1, MotionEvent e2,
                              float velocityX, float velocityY) {
        return false;
    }

    public void onShowPress(@NonNull MotionEvent e) {
    }

    public boolean onDown(@NonNull MotionEvent e) {
        return false;
    }

    public boolean onDoubleTap(@NonNull MotionEvent e) {
        return false;
    }

    public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
        return false;
    }

    public boolean onSingleTapConfirmed(@NonNull MotionEvent e) {
        return false;
    }

}

At least if you have multiple OnGestureListeners, you need to have only this one class in Java and your "real" coding is still in Kotlin.

user2808624
  • 2,502
  • 14
  • 28
0

This should work IMO. Not tested heavily but should work with both java and kotlin. Also, can be used by both that is either via custom GestureDetector.OnGestureListener or if class itself implement the GestureDetector.OnGestureListener

public interface SafeOnGestureListener extends GestureDetector.OnGestureListener {

  @Override
  boolean onScroll(@Nullable MotionEvent e1, @Nullable MotionEvent e2, float distanceX,
          
  @Override
  boolean onFling(@Nullable MotionEvent e1, @Nullable MotionEvent e2, float velocityX,
                  float velocityY);
}
Shubham AgaRwal
  • 4,355
  • 8
  • 41
  • 62
  • 1
    It's the same workaround I've presented in the question itself... The question was for Kotlin – android developer Jul 05 '23 at 08:39
  • Oops. Didn't read the question probably. Waiting for update from Android SDK team. If they can resolve via androidx libraries or if kotlin team do something about it. Didn't meant to answer though, just wanted to unblock people due to this. – Shubham AgaRwal Jul 05 '23 at 09:46
  • Also, this mean other answer by user2808624 meant the same as well :D – Shubham AgaRwal Jul 05 '23 at 09:47
0

Here's our temporary fix:

First the Class header / protocol declaration:

@Suppress("ABSTRACT_MEMBER_NOT_IMPLEMENTED")
class ActivityMain : AppCompatActivity(),
    GestureDetector.OnGestureListener

And second the method implementation:

@Suppress("ACCIDENTAL_OVERRIDE")
fun onScroll(
    p0: MotionEvent?,
    p1: MotionEvent,
    distanceX: Float,
    distanceY: Float
): Boolean {
    return true
}

Please note that there is no override before the onScroll implementation, since nothing is being overridden. And we added the "?" after MotionEvent.

As a result, "onScroll" will accept nulled-MotionEvents.