0

I am using android navigation on my project . On Some Xiaomi devices ,app crashes on startup. I could not find problem. code;

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/popoverNotification"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:background="@color/white"
        android:padding="10dp"
        android:visibility="visible"
        app:layout_constraintBottom_toTopOf="parent">

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/checkedImage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_emotion_checked_in_img" />

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/notificationTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:fontFamily="@font/montserrat_semibold"
            android:letterSpacing="-0.04"
            android:lineSpacingExtra="2sp"
            android:text="@string/emotion_checked_in"
            android:textColor="@color/emotion_info_title"
            android:textSize="14sp"
            android:textStyle="normal"
            app:layout_constraintBottom_toTopOf="@id/notificationDesc"
            app:layout_constraintStart_toEndOf="@id/checkedImage"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed"
            tools:text="Emotion Checked-in" />

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/notificationDesc"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="20dp"
            android:fontFamily="@font/montserrat_medium"
            android:letterSpacing="-0.04"
            android:lineSpacingExtra="4sp"
            android:text="@string/emotion_info_desc"
            android:textColor="#4e515d"
            android:textSize="12sp"
            android:textStyle="normal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@id/notificationTitle"
            app:layout_constraintTop_toBottomOf="@id/notificationTitle"
            app:layout_constraintVertical_chainStyle="packed"
            tools:text="You can track your daily emotions in the monthly view. " />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
package com.skylb.mooncalendar.app.main

import android.animation.Animator
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.view.MotionEvent
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.findNavController
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.Purchase
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import com.google.android.gms.common.GoogleApiAvailability
import com.skylb.mooncalendar.app.BuildConfig
import com.skylb.mooncalendar.app.R
import com.skylb.mooncalendar.app.core.BillingClientApi
import com.skylb.mooncalendar.app.core.BillingProduct
import com.skylb.mooncalendar.app.core.FlowEvents
import com.skylb.mooncalendar.app.core.NotificationApi
import com.skylb.mooncalendar.app.core.PurchaseState
import com.skylb.mooncalendar.app.core.onBillingNotAvailable
import com.skylb.mooncalendar.app.core.setOrderValidatedOnServer
import com.skylb.mooncalendar.app.core.setSubscriptionStatus
import com.skylb.mooncalendar.app.databinding.ActivityMainBinding
import com.skylb.mooncalendar.app.events.AppLanguageChangedEvent
import com.skylb.mooncalendar.app.events.BottomNavigationShowItemEvent
import com.skylb.mooncalendar.app.events.NotificationPopoverEvent
import com.skylb.mooncalendar.app.events.OnSubscriptionStatusChangedEvent
import com.skylb.mooncalendar.app.events.SendSubscriptionRequestEvent
import com.skylb.mooncalendar.app.exceptions.BillingNotAvailableException
import com.skylb.mooncalendar.app.firebase.AnalyticsManager
import com.skylb.mooncalendar.app.firebase.EVENT_RESTORED_TRIAL
import com.skylb.mooncalendar.app.firebase.EVENT_STARTED_TRIAL
import com.skylb.mooncalendar.app.firebase.FcmMessagingManager
import com.skylb.mooncalendar.app.network.request.PurchaseRequestModel
import com.skylb.mooncalendar.app.network.response.SubscriptionStatus
import com.skylb.mooncalendar.app.ui.custom.AnimatorListenerWrapper
import com.skylb.mooncalendar.app.util.AppUtil
import com.skylb.mooncalendar.app.util.DeviceUtil
import com.skylb.mooncalendar.app.util.NotificationUtil
import com.skylb.mooncalendar.app.util.dpToPx
import com.skylb.mooncalendar.app.util.setAttributes
import dagger.Lazy
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : BaseActivity() {

    private lateinit var binding: ActivityMainBinding
    private var notificationViewY = 0f
    private val hideNotification = MutableSharedFlow<Unit>()

    private val mainViewModel: MainViewModel by viewModels()

    @Inject
    lateinit var billingClientApi: BillingClientApi

    @Inject
    lateinit var notificationApi: NotificationApi

    @Inject
    lateinit var fcmMessagingManager: FcmMessagingManager

    @Inject
    lateinit var analyticsManager: Lazy<AnalyticsManager>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        onCreateActions()
        setContentView(binding.root)
        initView()
    }

    private val navigationController: NavController get() = findNavController(R.id.nav_host_fragment_activity_main)

    override fun onSupportNavigateUp(): Boolean {
        return navigationController.navigateUp() || super.onSupportNavigateUp()
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun initNotificationView() {
        binding.popoverNotification.run {
            background = GradientDrawable().apply { setAttributes(context, R.color.white, 20f) }
            var shouldClick = false
            setOnTouchListener { v, event ->
                when (event.action) {
                    MotionEvent.ACTION_DOWN -> {
                        shouldClick = true
                        notificationViewY = event.y
                    }
                    MotionEvent.ACTION_MOVE -> {
                        shouldClick = false
                        if (event.y < notificationViewY) {
                            hideNotification(true)
                        }
                    }
                    MotionEvent.ACTION_UP -> {
                        if (shouldClick) {
                            v.performClick()
                        }
                    }
                }
                true
            }
        }
    }

    private fun onCreateActions() {
        GoogleApiAvailability.getInstance().makeGooglePlayServicesAvailable(this)
        fcmMessagingManager.checkFcmToken()
        notificationApi.createDefaultChannelsIfNotExist()

        lifecycleScope.launch(Dispatchers.IO) {
            AdvertisingIdClient.getAdvertisingIdInfo(this@MainActivity).id?.let {
                mainViewModel.setIdfa(DeviceUtil.getDeviceId(this@MainActivity), it)
            }
        }
    }

    private fun showNotification(event: NotificationPopoverEvent) {
        binding.run {
            hideNotification()
            notificationTitle.setText(event.titleRes)
            notificationDesc.setText(event.messageRes)
        }
        binding.popoverNotification.run {
            if (event.navigateAction != null) {
                setOnClickListener {
                    navigationController.navigate(event.navigateAction)
                }
            } else {
                setOnClickListener(null)
            }
            animate().translationY((height + 30f.dpToPx()).toFloat()).setListener(object : AnimatorListenerWrapper() {
                override fun onAnimationEnd(p0: Animator) {
                    super.onAnimationEnd(p0)
                    lifecycleScope.launch {
                        hideNotification.emit(Unit)
                    }
                }
            }).setDuration(300)
        }
    }

    private fun hideNotification(withAnim: Boolean = false) {
        binding.popoverNotification.run {
            clearAnimation()
            val translationY = (height + 30f.dpToPx()).toFloat().unaryMinus()
            if (withAnim) {
                animate().translationY(translationY).setDuration(300)
            } else {
                setTranslationY(translationY)
            }
        }
    }

    private fun initView() {
        initNotificationView()
        lifecycleScope.launch(Dispatchers.IO) {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                checkBillingHistory()
                sendCheck()
                mainViewModel.getSettingsData()
            }
        }
        //drop means skip initial value
        billingClientApi.purchaseState.drop(1).onEach {
            runCatching {
                checkPurchase(it)
            }.onFailure {
                withContext(Dispatchers.Main) {
                    Toast.makeText(this@MainActivity, it.message, Toast.LENGTH_SHORT).show()
                }
            }
        }.flowOn(Dispatchers.IO).launchIn(lifecycleScope)

        FlowEvents.listen<SendSubscriptionRequestEvent>().onEach {
            sendSubscriptionRequestToServer(it)
        }.launchIn(lifecycleScope)

        FlowEvents.listen<NotificationPopoverEvent>().onEach {
            showNotification(it)
        }.flowOn(Dispatchers.Main).launchIn(lifecycleScope)

        FlowEvents.listen<OnSubscriptionStatusChangedEvent>().onEach {
            if (it.hasSubscription) {
                showNotification(NotificationPopoverEvent(R.string.your_subscription_started_title, R.string.your_trial_started_message))
            }
        }.flowOn(Dispatchers.Main).launchIn(lifecycleScope)

        hideNotification.debounce(2000).flowOn(Dispatchers.IO).onEach {
            hideNotification(true)
        }.flowOn(Dispatchers.Main).launchIn(lifecycleScope)

        mainViewModel.configResponse.observe(this) {
            lifecycleScope.launch(Dispatchers.IO) {
                AppUtil.setGeneralConfigData(this@MainActivity, it.data)
            }
        }
        FlowEvents.listen<AppLanguageChangedEvent>().onEach {
            startActivity(Intent(this, MainActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            })
        }.launchIn(lifecycleScope)

        mainViewModel.settingsData.observe(this) {
            AppUtil.setSettingsDataToPref(this@MainActivity, it)
        }
        mainViewModel.generalConfig()
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.let {
            handleIntent(it)
        }
    }

    private fun handleIntent(intent: Intent) {
        intent.getStringExtra(NotificationUtil.NOTIFICATION_CATEGORY)?.let { category ->
            if (NotificationUtil.featureChannelTypes.contains(category)) {
                lifecycleScope.launch {
                    FlowEvents.publish(BottomNavigationShowItemEvent(NotificationUtil.getMenuIdFromCategory(category)))
                }
            }
        }
    }

    private fun sendSubscriptionRequestToServer(event: SendSubscriptionRequestEvent) {
        val purchase = event.purchase
        mainViewModel.sendPurchase(PurchaseRequestModel(
            DeviceUtil.getDeviceId(this), purchase.purchaseToken
        ).apply {
            val purchasedProductId = purchase.products[0]
            if (purchasedProductId == BillingProduct.LIFETIME_SUB.productId) {
                productId = purchasedProductId
            } else {
                subscriptionId = purchasedProductId
            }
        }).observe(this) {
            if (it.status?.success == true) {
                sendTrialAnalyticEvent(event.afterRestore)
                onSubscriptionSuccess(event.purchase, it.data!!.androidSubscription)
            }
        }
    }

    private fun sendTrialAnalyticEvent(isRestore: Boolean) {
        analyticsManager.get().logEvent(if (isRestore) EVENT_RESTORED_TRIAL else EVENT_STARTED_TRIAL)
    }

    private fun onSubscriptionSuccess(purchase: Purchase, subscriptionStatus: SubscriptionStatus?) {
        lifecycleScope.launch(Dispatchers.IO) {
            setOrderValidatedOnServer(this@MainActivity, purchase.orderId)
            setSubscriptionStatus(this@MainActivity, true, subscriptionStatus)
        }
    }

    private fun checkPurchase(state: PurchaseState) {
        if (state is PurchaseState.Success) {
            billingClientApi.checkSubscription(this, state.purchases, true)
        } else if (state is PurchaseState.Error && state.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            throw Exception(getString(R.string.you_can_restore_your_purchase))
        } else {
            throw Exception(getString(R.string.an_error_occurred_while_purchase))
        }
    }

    private fun sendCheck() {
        try {
            mainViewModel.check(
                BuildConfig.VERSION_NAME, DeviceUtil.getCountryCode(this), DeviceUtil.getDeviceId(this@MainActivity)
            )
        } catch (th: Throwable) {
            th.printStackTrace()
        }
    }

    private fun checkBillingHistory() {
        billingClientApi.queryPurchasesAsyncFlow().catch { th ->
            if (th is BillingNotAvailableException) {
                onBillingNotAvailable(this@MainActivity)
            }
            th.printStackTrace()
        }.onEach {
            billingClientApi.checkSubscription(this, it, false)
        }.flowOn(Dispatchers.IO).launchIn(lifecycleScope)
    }
}

Fatal Exception: java.lang.RuntimeException Unable to start activity ComponentInfo{com.skylb.mooncalendar.app/com.skylb.mooncalendar.app.main.MainActivity}: android.view.InflateException: Binary XML file line #19 in com.skylb.mooncalendar.app:layout/activity_main: Binary XML file line #19 in com.skylb.mooncalendar.app:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView

java.lang.IllegalStateException - Could not find Navigator with name "navigation". You must call NavController.addNavigator() for each navigation type.

note : Proguard is enabled

ahmetvefa53
  • 581
  • 6
  • 20
  • I'm not sure why, but I would try to replace the FragmentContainerView with FrameLayout in your activity_main.xml. it happened to me once and that's what I did... I would like to know what is the real reason for this issue, I believe it's happen because Xiaomi devices missing that component but I can't say it for sure. – Royi Feb 01 '23 at 15:15

0 Answers0