1

I try to handle insets by setting ViewCompat.setOnApplyWindowInsetsListener on attached view, but listener is not called.

I'm confused, because when i apply insets listener to decorView, it works:

   ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets  ->
        Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
        insets
    }

But when i apply insets listener to activity root view (i do it in same place), it's not working:

ViewCompat.setOnApplyWindowInsetsListener(main_root) { v, insets  ->
        Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
        insets
    }

I have a single activity app, and i set both this listeners when onCreate() method called.

I know i can get insets from first example and cache them, but now i need second code works, because i need this library works:

https://github.com/chrisbanes/insetter

It's have api like:

    Insetter.builder()
        .applySystemWindowInsetsToPadding(Side.LEFT)
        .applyToView(main_bottom_nav)

It provide easy way to apply insets to your views. But it did't work.

I debug it and i saw that under the hood this library set listener to view like i try to do in a second code example, so when i get the reason why my listeners are not trigger, i think i can make this library work.

My minds are:

I don't rly understand what's happening. It's very strange for me that decorView has insets, but activity root is not. By logic, it's possible only when someone between decorView and activity view handle and consume insets, but is sounds strange: activity root view must to know about insets.

What i tried to do(see code), and it's not help:

  • Change root ViewGroup from ConstraintLayout ro Frame/Linear/RelativeLayout.
  • Add/remove fitsSystemWindows attribute to activity root ViewGroup. In fact i planning to remove insets at all to make a fullscreen application, so i don't think i need this flag enabled.
  • Change windowSoftInputMode of activity: it's adjustResize now.
  • Call requestApplyInsets() on target view before and after listener applied.
  • Downgrade androidx.core:core-ktx and androidx.activity:activity-ktx to stable versions.

Code:

activity.xml: https://github.com/KirstenLy/AdviceCollector/blob/master/app/src/main/res/layout/activity_main.xml

<?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/main_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_main_content">

    <ImageView
        android:id="@+id/main_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/wise"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_bottom_nav"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/color_main_content"
        android:elevation="@dimen/elevation_4"
        android:translationZ="@dimen/elevation_4"
        android:visibility="gone"
        app:labelVisibilityMode="unlabeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_navigation_menu" />

    <!-- Helper view to show snackBar: need because of transparent navigation bar -->
    <View
        android:id="@+id/main_snackbar_anchor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <!-- No internet error section -->
    <ImageView
        android:id="@+id/main_error_internet_icon"
        android:layout_width="@dimen/image_size_96"
        android:layout_height="@dimen/image_size_96"
        app:layout_constraintBottom_toTopOf="@+id/main_error_internet_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        app:srcCompat="@drawable/ic_wifi" />

    <TextView
        android:id="@+id/main_error_internet_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:gravity="center"
        android:lines="3"
        android:padding="@dimen/padding_16"
        android:textAlignment="center"
        app:layout_constraintBottom_toTopOf="@+id/main_error_internet_retry_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/main_error_internet_icon"
        tools:text="@string/main_activity_error_loading" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/main_error_internet_retry_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:backgroundTint="@color/dark_blue"
        android:padding="@dimen/padding_16"
        android:text="@string/default_retry"
        app:icon="@drawable/ic_autorenew"
        app:iconGravity="textEnd"
        app:iconPadding="@dimen/padding_4"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/main_error_internet_text" />

    <androidx.constraintlayout.widget.Group
        android:id="@+id/main_error_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="main_error_internet_icon,main_error_internet_retry_button,main_error_internet_text" />

    <FrameLayout
        android:id="@+id/main_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/main_bottom_nav"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt: https://github.com/KirstenLy/AdviceCollector/blob/master/app/src/main/java/com/kirstenly/advice_collector/ui/activity_main/MainActivity.kt

package com.kirstenly.advice_collector.ui.activity_main

import android.os.Bundle
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.google.android.play.core.review.ReviewManagerFactory
import com.kirstenly.advice_collector.R
import com.kirstenly.advice_collector.Screens
import com.kirstenly.advice_collector.contracts.NavigationEventConsumer
import com.kirstenly.advice_collector.other.helpers.DayNightThemeHelper
import com.kirstenly.advice_collector.other.helpers.InsetHelper
import com.kirstenly.advice_collector.ui.activity_main.router.MainActivityRouter
import com.kirstenly.sdk.extensions.*
import com.kirstenly.sdk.other.navigation.BaseScreen
import dagger.android.support.DaggerAppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject

/** Host activity for application */
class MainActivity : DaggerAppCompatActivity(R.layout.activity_main), NavigationEventConsumer {

    @Inject lateinit var router: MainActivityRouter
    @Inject lateinit var viewModel: MainActivityViewModel
    @Inject lateinit var insetHelper: InsetHelper
    @Inject lateinit var dayNightThemeHelper: DayNightThemeHelper

    override fun onNavigationEvent(screen: BaseScreen) {
        router.navigateTo(screen)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setTheme(R.style.AppTheme)
        initViews()
        initObservers()
    }

    private fun initViews() {

        ViewCompat.setOnApplyWindowInsetsListener(window.decorView){ v, insets  ->
            Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
            insets
        }
        
//        main_bottom_nav.applySystemWindowInsetsToMargin(bottom = true, top = true, left = true, consume = true)

//        root_root.requestApplyInsetsWhenAttached()
//        Insetter.builder()
//            .applySystemWindowInsetsToPadding(Side.LEFT)
//            .applyToView(main_bottom_nav)
//
//        setWindowTransparency { statusBarSize, navigationBarSize ->
//            insetHelper.topInset = statusBarSize
//            insetHelper.bottomInset = navigationBarSize
//            main_bottom_nav.updatePaddingFromPx(bottom = navigationBarSize)
//            main_snackbar_anchor.layoutParams.height = navigationBarSize
//        }

        with(main_bottom_nav) {
            setOnNavigationItemSelectedListener {
                when (it.itemId) {
                    R.id.bottom_home -> router.navigateTo(Screens.HomeScreen)
                    R.id.bottom_favorite -> router.navigateTo(Screens.FavoriteAdvicesScreen)
                    R.id.bottom_login -> router.navigateToLoginOrUserAdvices()
                    R.id.bottom_new_advice -> router.navigateToNewAdvice()
                    R.id.bottom_day_night -> dayNightThemeHelper.setDayOrNightMode()
                }
                true
            }

            removeItemIconTintList()
            setIconOnItem(R.id.bottom_day_night, dayNightThemeHelper.getDayOrNightModeIcon())
        }

        main_error_internet_retry_button.setOnClickListener {
            viewModel.retryInternetConnection()
        }
    }

    private fun initObservers() {
        viewModel.errorStateLiveData.observe(this, { errorState ->
            main_error_group.isVisible = errorState != ErrorState.NONE
            main_icon.isVisible = errorState == ErrorState.NONE
            main_error_internet_retry_button.isEnabled = errorState != ErrorState.RETRYING

            val errorText = when (errorState) {
                ErrorState.RETRYING -> getString(R.string.main_activity_error_retrying)
                ErrorState.NO_INTERNET_ERROR -> getString(R.string.main_activity_error_loading)
                ErrorState.UPDATE_DATA_ERROR -> getString(R.string.main_activity_error_update)
                ErrorState.NONE -> null
            }

            main_error_internet_text.text = errorText
        })

        viewModel.navigationEventLiveData.observe(this, router::navigateTo)

        viewModel.isDataPreparedLiveData.observe(this, { isDataPrepared ->
            main_icon.isGone = isDataPrepared
        })
    }

    // TODO: Поддержать inAppReview, будет возможно только после залива в GP
    private fun initAppReview() {
        val manager = ReviewManagerFactory.create(this)
        val request = manager.requestReviewFlow()
        request.addOnCompleteListener { request ->
            if (request.isSuccessful) {
                // We got the ReviewInfo object
                val reviewInfo = request.result
                val flow = manager.launchReviewFlow(this, reviewInfo)
                flow.addOnCompleteListener { a ->
                    a.isComplete
                    // The flow has finished. The API does not indicate whether the user
                    // reviewed or not, or even whether the review dialog was shown. Thus, no
                    // matter the result, we continue our app flow.
                }
            } else {
                // There was some problem, continue regardless of the result.
            }
        }
    }

    override fun onBackPressed() {
        if (!router.handleOnBackPressedByCurrentFragment()) {
            if (!supportFragmentManager.isBackStackEmpty()) {
                super.onBackPressed()
            } else {
                finish()
            }
        }
    }
}
KirstenLy
  • 1,172
  • 3
  • 14
  • 25

3 Answers3

2

In order for the insets listener to be called when using:

ViewCompat.setOnApplyWindowInsetsListener(main_root) { v, insets  ->
        ...
        insets
    }

the main_root view in the layout.xml needs to have the android:fitsSystemWindows="true" defined, or you need to have the following in your app-theme:

android:fitsSystemWindows="true"
SteveC
  • 1,594
  • 2
  • 14
  • 14
0

For me what worked was setting the listener on the decorView of the window

ViewCompat.setOnApplyWindowInsetsListener(window.decorView)
amitfr
  • 1,033
  • 1
  • 9
  • 29
-2

Ok, fixed when i add:

true

in my application theme.

If i understand it correctly, without this attribute decorView consume insets.

¯_(ツ)_/¯

KirstenLy
  • 1,172
  • 3
  • 14
  • 25