Search code examples
androidkotlinlottie

Lottie Stack Overflow


I am programming a live stream function and the button is animated with lottie. I can't find anything wrong on the application but crashlytics shows this.

Fatal Exception: java.lang.StackOverflowError: stack size 8192KB
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.ViewGroup.findViewTraversal(ViewGroup.java:4745)
       at android.view.View.findViewById(View.java:24941)
       at android.view.Window.findViewById(Window.java:1481)
       at androidx.appcompat.app.AppCompatDelegateImpl.findViewById(AppCompatDelegateImpl.java:634)
       at androidx.appcompat.app.AppCompatActivity.findViewById(AppCompatActivity.java:259)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
       at com.zurdo.presentation.features.home.HomeActivity.showLiveViewer(HomeActivity.kt:154)
       at com.zurdo.presentation.features.home.HomePresenter$onRefreshStatusLive$1$2.invoke(HomePresenter.kt:124)
       at com.zurdo.presentation.features.home.HomePresenter$onRefreshStatusLive$1$2.invoke(HomePresenter.kt:120)
       at com.zurdo.core.functional.Either.either(Either.kt:28)
       at com.zurdo.presentation.features.home.HomePresenter$onRefreshStatusLive$1.invoke(HomePresenter.kt:120)
       at com.zurdo.presentation.features.home.HomePresenter$onRefreshStatusLive$1.invoke(HomePresenter.kt:119)
       at com.zurdo.core.interactor.UseCase$invoke$2$1.invokeSuspend(UseCase.kt:17)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:223)
       at android.app.ActivityThread.main(ActivityThread.java:7680)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Here is the code

Activity_main.xml:

<com.airbnb.lottie.LottieAnimationView
            android:id="@+id/btnLiveUser"
            android:layout_width="0dp"
            android:layout_height="60dp"
            android:elevation="10dp"
            android:layout_marginStart="5dp"
            android:layout_marginEnd="5dp"
            android:visibility="gone"
            app:layout_constraintTop_toTopOf="@id/goToStoreButton"
            app:layout_constraintEnd_toStartOf="@+id/goToStoreButton"
            app:layout_constraintStart_toEndOf="@id/publicationsButton"
            app:layout_constraintBottom_toBottomOf="@id/goToStoreButton"
            app:lottie_rawRes="@raw/btn_live_1"/>

HomeActivity.kt:

package com.zurdo.presentation.features.home

import android.animation.Animator
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.InstallStatus
import com.zurdo.R
import com.zurdo.core.exception.Failure
import com.zurdo.core.extension.getDynamicLinkId
import com.zurdo.core.platform.BaseActivity
import com.zurdo.core.platform.Constants
import com.zurdo.core.platform.NavigationHelper
import com.zurdo.core.platform.PreferenceHelper
import com.zurdo.data.models.Publication
import com.zurdo.data.models.User
import com.zurdo.presentation.ZurdoApp
import com.zurdo.presentation.features.onboarding.OnboardingActivity
import com.zurdo.presentation.features.publicationDetail.publicationExtra
import com.zurdo.presentation.features.publications.PublicationListFragment
import com.zurdo.presentation.features.publications.pubDetaiRequestCode
import com.zurdo.presentation.features.support.supportRequestCode
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.custom_search_field.*
import java.util.*
import javax.inject.Inject
import com.zurdo.presentation.features.publications.ReqCodeComments
import com.zurdo.presentation.features.videostream.HOST
import com.zurdo.presentation.features.videostream.IS_LIVE


const val publicationListFragmentTag: String = "publicationListFragment"

class HomeActivity : BaseActivity(),
    HomeContract.View {

    private val UPDATE_CODE: Int = 100
    private lateinit var appUpdateManager: AppUpdateManager

    @Inject
    lateinit var presenter: HomeContract.Presenter
    private lateinit var publicationListFragment: PublicationListFragment

    @Inject
    lateinit var preferences : PreferenceHelper

    private val manager: FragmentManager = supportFragmentManager

    override fun layoutId() = R.layout.activity_main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ZurdoApp.component.inject(this)

        if(!preferences.getBoolean(Constants.SPKEY_IS_NOT_FIRST_LAUNCH)){
            startActivity(Intent(this, OnboardingActivity::class.java))
        }

        presenter.view = this
        presenter.onCreate()

        btnLiveUser.setOnClickListener {
            goToStreamViewer()
        }

        //intent?.let {
        //    val isLive = intent.getStringExtra("title") == "Zurdo is live testing!" //TODO(Temporal validation)
//
        //}
    }

    private fun goToStreamViewer() {
        NavigationHelper.goToStreamView(this, false)
    }

    override fun goToStreamView() {
        NavigationHelper.goToStreamView(this, true)
    }

    override fun onNewIntent(intent: Intent?) {
        if (intent?.getBooleanExtra(HOST, true) == false) {
            if (intent.getBooleanExtra(IS_LIVE, false)) {
                presenter.verifyLiveActive()
                goToStreamViewer()
            }
        }
        if (!preferences.userToken.isNullOrEmpty()) {
            intent.getDynamicLinkId { id, isPotCast ->
                goToPublication(id, isPotCast)
                this.intent = null
            }
        }
        super.onNewIntent(intent)
    }

    override fun showLiveHost() {
        goToLiveButton.isVisible = true
        ivBtnLive.isVisible = true
    }

    override fun showLiveViewerTemporal() {
        btnLiveUser.isVisible = true
        tvLiveUser.isVisible = true

    }

        override fun onResume() {
            super.onResume()
            presenter.onRefreshStatusLive()

        //checkIfUserIsPremium()
        //appUpdateManager = AppUpdateManagerFactory.create(this)
//
        //// Returns an intent object that you use to check for an update.
        //val appUpdateInfoTask = appUpdateManager.appUpdateInfo
//
        //// Checks that the platform will allow the specified type of update.
        //appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
        //    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
        //        try {
        //            appUpdateManager.startUpdateFlowForResult(
        //                appUpdateInfo,
        //                AppUpdateType.IMMEDIATE,
        //                this,
        //                UPDATE_CODE
        //            )
        //        } catch (e: IntentSender.SendIntentException) {
        //            e.printStackTrace()
        //        }
        //    }
        //}

        if (!preferences.userToken.isNullOrEmpty()) {
            intent.getDynamicLinkId { id, isPotCast ->
                goToPublication(id, isPotCast)
                intent = null
            }
        }
    }

    override fun showLiveViewer(show: Boolean) {
        if(show) {
            btnLiveUser.addAnimatorUpdateListener {
                tvLiveUser.isVisible = (btnLiveUser.progress * 100).toInt() < 90
            }
            btnLiveUser.addAnimatorListener(object : Animator.AnimatorListener {
                override fun onAnimationStart(p0: Animator?) { println() }

                override fun onAnimationEnd(p0: Animator?) {
                    btnLiveUser.playAnimation()
                }

                override fun onAnimationCancel(p0: Animator?) { println() }

                override fun onAnimationRepeat(p0: Animator?) { println() }
            })
            btnLiveUser.playAnimation()
        } else {
            btnLiveUser.removeAllAnimatorListeners()
            btnLiveUser.pauseAnimation()
        }
        btnLiveUser.isVisible = show
        tvLiveUser.isVisible = show
    }

    private fun checkIfUserIsPremium(){
        if(preferences.isUserPremium)
            layoutProfileButton.background = resources.getDrawable(R.drawable.bg_profile_border)
        else{
            layoutProfileButton.background = null
        }
    }

    override fun onDestroy() {
        presenter.onDestroy()
        super.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == supportRequestCode && resultCode == Activity.RESULT_OK) {
            notify(R.string.sent_support_contact_text, coordinator)
        }else if(requestCode == pubDetaiRequestCode){
            //publicationListFragment.presenter.onHomeTapped()
            //publicationListFragment.presenter.fetchPublications(isHome = false, withProgress = false)
            //publicationListFragment.presenter.refreshPublications(false)
            publicationListFragment.updatePublication((data?.getSerializableExtra(publicationExtra) as Publication))
        }else if(requestCode == ReqCodeComments){
            publicationListFragment.updatePublication((data?.getSerializableExtra(publicationExtra) as Publication))
        }

    }

    override fun setupView() {
        publicationListFragment = PublicationListFragment.newInstance()
        manager.beginTransaction()
            .replace(
                R.id.containerLayout,
                publicationListFragment,
                publicationListFragmentTag
            )
            .commit()
        searchButton.setOnClickListener {
            presenter.onShowSearchField(true)
        }
        closeButton.setOnClickListener {
            presenter.onShowSearchField(false)
        }
        profileButton.setOnClickListener {
            presenter.onProfileButtonTapped()
        }
        goToStoreButton.setOnClickListener {
            presenter.onGoToStoreTapped()
        }

        goToLiveButton.setOnClickListener { presenter.onGoToStreamTapped() }

        publicationsButton.setOnClickListener {
            //publicationListFragment.presenter.onSearchPublicationsByFilter()
            publicationListFragment.presenter.onHomeTapped()
            //publicationListFragment.presenter.fetchPublications(isHome = true, withProgress = true)
        }
        premiumButton.setOnClickListener {
            presenter.onGoToStoreTapped()
        }
        logoHomeBtn.setOnClickListener{
            publicationListFragment.presenter.onHomeTapped()
        }
        /*
        publicationsButton.setOnTouchListener(OnTouchListener { v, me ->
            if (me.action == MotionEvent.ACTION_DOWN) {

            } else if (me.action == MotionEvent.ACTION_MOVE) {
                val params = ViewGroup.LayoutParams(
                    v.width,
                    v.height,
                    (me.rawX - v.width / 2).toInt(),
                    (me.rawY - v.height).toInt()
                )
                v.layoutParams = params
            }
            true
        })
        */
        searchField.setOnEditorActionListener(object : TextView.OnEditorActionListener {
            override fun onEditorAction(p0: TextView?, p1: Int, p2: KeyEvent?): Boolean {
                if (p1 == EditorInfo.IME_ACTION_SEARCH) {
                    p0?.text.toString().let {
                        presenter.onSearchPublications(it.toLowerCase(Locale.getDefault()))
                    }
                    return true
                }
                return false
            }
        })
    }

    override fun showCustomSearchField(show: Boolean) {
        customSearchField.visibility = if (show) View.VISIBLE else View.GONE
        searchField.setText("")

        if (show) {
            searchField.requestFocus()
            showKeyboard()
        } else {
            hideKeyboard()
            //publicationListFragment.presenter.fetchPublications(withProgress = false)
            publicationListFragment.presenter.refreshPublications()
        }
    }

    override fun showProfilePicture(user: User?) {
        user?.let {
            Glide.with(this)
                .load(it.profileUrl)
                .transform(CircleCrop())
                .into(profileButton)

            checkIfUserIsPremium()
        }
    }

    override fun goToStoreView() = NavigationHelper.goToStoreView(this)

    override fun goToProfileView() {
        NavigationHelper.goToProfileView(this)
    }

    override fun searchPublications(filter: String) {
        hideKeyboard()
        publicationListFragment.presenter.onSearchPublicationsByFilter(filter)
    }

    override fun handleFailure(failure: Failure) {
        val message = when (failure) {
            Failure.DatabaseError -> R.string.unable_to_get_user
            else -> R.string.general_error_text
        }
        notify(message, coordinator)
    }

    var appUpdatedListener: InstallStateUpdatedListener = object : InstallStateUpdatedListener {
        override fun onStateUpdate(installState: InstallState) {
            if (installState.installStatus() == InstallStatus.DOWNLOADED) {
                notify(R.string.app_update_downloaded,coordinator)
            } else if (installState.installStatus() == InstallStatus.INSTALLED) {
                appUpdateManager.unregisterListener(this)
            }
        }
    }

    private fun goToPublication(id: Int, isPotCast: Boolean) {
        if (isPotCast) NavigationHelper.goToPublicationDetailPodcastView(this, id, 2091)
        else NavigationHelper.goToPublicationDetailView(this, id, 2091)

    }
}

Could someone help telling me how to reproduce the issue and solve it? Thanks in advance

I have tried making multiple calls to overload the memory stack but nothing happened


Solution

  • Looking at your stacktrace, we see this recurring bit:

       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
    
       at com.zurdo.presentation.features.home.HomeActivity$showLiveViewer$2.onAnimationEnd(HomeActivity.kt:147)
       at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:554)
       at com.airbnb.lottie.utils.BaseLottieAnimator.notifyEnd(BaseLottieAnimator.java:74)
       at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
       at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
       at com.airbnb.lottie.LottieAnimationView.playAnimation(LottieAnimationView.java:599)
    
       at com.zurdo.presentation.features.home.HomeActivity.showLiveViewer(HomeActivity.kt:154)
    

    So you're stuck in a loop because of the code that calls playAnimation in onAnimationEnd:

    override fun onAnimationEnd(p0: Animator?) {
        btnLiveUser.playAnimation()
    }
    

    That call to the LottieDrawable is immediately triggering a call to end animation, so your callback gets called, which calls playAnimation, which immediately ends the animation, etc, etc:

    at com.airbnb.lottie.utils.LottieValueAnimator.endAnimation(LottieValueAnimator.java:221)
    at com.airbnb.lottie.LottieDrawable.playAnimation(LottieDrawable.java:593)
    

    So why is this?

    Looking at the source code for that class, we see:

    ...
    if (!animationsEnabled()) {
      setFrame((int) (getSpeed() < 0 ? getMinFrame() : getMaxFrame()));
      animator.endAnimation(); // <-- KEY BIT HERE
      if (!isVisible()) {
        onVisibleAction = OnVisibleAction.NONE;
      }
    }
    ...
    

    So it seems Lottie calls the animation end callback immediately if the animation is actually disabled. OK. So why is your animation disabled? Again, according to the source code:

    private boolean animationsEnabled() {
        return systemAnimationsEnabled || ignoreSystemAnimationsDisabled;
    }
    

    So it seems animations are disabled if the Android system animations are disabled and you haven't told Lottie to ignore that.

    So it would seem that your crash occurs when you disable system animations. This should be easy to test and confirm.