0

I have problem with backstack and toolbar. I have a singchat fragment and a user overlay. When I switch from the singchat fragment to the user overlay, I disable the mToolbar that is passed to all fragments from MainActivity and use AppBarLayout with scrollable behavior instead it. When I press "Negative Up" I go back to singlechat from the toolbar, but when I want to go back to the previous ContactFragment fragment, the toolbar switches to the mainToolbar and the next time I go to the fragments, the tools on the toolbar (such as Negative Up or Options Menu) disappear. (I think this is due to incorrect backstack configuration) Is there a way to fix this without changing the architecture of the application (without removing the toolbar from activity_main.xml and creating a toolbar for each fragment)? I will attach the code fragments below.

class SingleChatFragment(private val contact: CommonModel) :
    BaseFragment(R.layout.fragment_single_chat) {
    private lateinit var binding: FragmentSingleChatBinding
    private lateinit var mListenerInfoToolbar: AppValueEventListener
    private lateinit var mReceivingUser: User
    private lateinit var mToolbarInfo: View
    private lateinit var mRefUser: DatabaseReference
    private lateinit var mRefMessages: DatabaseReference
    private lateinit var mAdapter: SingleChatAdapter
    private lateinit var mRecyclerView: RecyclerView
    private lateinit var mMessagesListener: AppChildEventListener
    private lateinit var mLayoutManager: LinearLayoutManager
    private var mCountMessages = 20
    private var mIsScrolling = false
    private var mSmoothScrollToPosition = true

    private val imageMultiMedia = registerForActivityResult(ActivityResultContracts
        .PickMultipleVisualMedia(10)) { uris ->
        if(uris.isNotEmpty()) {
            Log.d("Photo Picker", "Items count ${uris.size}")
            CoroutineScope(Dispatchers.IO).launch {
                uploadImagesToDatabase(uris)
            }
        } else {
            Log.d("Photo Picker", "No media")
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentSingleChatBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onResume() {
        super.onResume()
        initFields()
        initToolbar()
        initRecyclerView()
        mToolbarInfo.setOnClickListener { replaceFragment(UserOverlay(mReceivingUser)) }

    }

    private fun initFields() {
        mLayoutManager = LinearLayoutManager(this.context)
        binding.chatInputMessage.addTextChangedListener(AppTextWatcher{
            val string = binding.chatInputMessage.text.toString()
            if(string.isEmpty()) {
                binding.chatBtnSendMessage.visibility = View.GONE
                binding.chatBtnAttach.visibility = View.VISIBLE
            } else {
                binding.chatBtnSendMessage.visibility = View.VISIBLE
                binding.chatBtnAttach.visibility = View.GONE
            }
        })

        binding.chatBtnAttach.setOnClickListener { openImagePicker() }
    }

    private fun openImagePicker() {
        val mediaSelectionMax = 10
        val intent = Intent(MediaStore.ACTION_PICK_IMAGES)
        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaSelectionMax)
        imageMultiMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo))
    }

    private fun uploadImagesToDatabase(uris: List<Uri>) {
        for(uri in uris) {
            val messageKey = REF_DATABASE_ROOT.child(NODE_MESSAGES).child(contact.id).push().key.toString()
            val path = REF_STORAGE_ROOT.child(FOLDER_MESSAGES_IMAGES).child(messageKey)
            val imageFileName = UUID.randomUUID().toString()
            val imageRef = path.child(imageFileName)
            imageRef.putFile(uri)
                .addOnSuccessListener {
                    imageRef.downloadUrl.addOnSuccessListener {
                        sendMessageAsImage(contact.id, it.toString(), messageKey)
                    }
                }
        }
    }

    private fun initRecyclerView() {
        mRecyclerView = binding.chatRecycleView
        mAdapter = SingleChatAdapter(requireContext())
        mRefMessages = REF_DATABASE_ROOT.child(NODE_MESSAGES).child(UID).child(contact.id)
        mRecyclerView.adapter = mAdapter
        mRecyclerView.layoutManager = mLayoutManager
        mRecyclerView.setHasFixedSize(true)
        mRecyclerView.isNestedScrollingEnabled = false
        mMessagesListener = AppChildEventListener {
            val message = it.getCommonModel()
            if(mSmoothScrollToPosition) {
                mAdapter.addItemToBottom(message) {
                    mRecyclerView.smoothScrollToPosition(mAdapter.itemCount)
                }
            } else {
                mAdapter.addItemToTop(message)
            }
        }

        mRefMessages.limitToLast(mCountMessages).addChildEventListener(mMessagesListener)

        mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    mIsScrolling = true
                }
            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (mIsScrolling && dy < 0 && mLayoutManager.findFirstVisibleItemPosition() <= 3) {
                    updateData()
                }
            }
        })
    }

    private fun updateData() {
        mSmoothScrollToPosition = false
        mIsScrolling = false
        mCountMessages += 10
        mRefMessages.removeEventListener(mMessagesListener)
        mRefMessages.limitToLast(mCountMessages).addChildEventListener(mMessagesListener)
    }

    private fun initToolbar() {
        mToolbarInfo = APP_ACTIVITY.mToolbar.findViewById<ConstraintLayout>(R.id.toolbar_info)
        mToolbarInfo.visibility = View.VISIBLE
        mListenerInfoToolbar = AppValueEventListener {
            mReceivingUser = it.getUserModel()
            initInfoToolbar()
        }


        mRefUser = REF_DATABASE_ROOT.child(NODE_USERS).child(contact.id)
        mRefUser.addValueEventListener(mListenerInfoToolbar)
        binding.chatBtnSendMessage.setOnClickListener {
            mSmoothScrollToPosition = true
            val message = binding.chatInputMessage.text.toString()
            if (message.isEmpty()) {
                showToast("Input message")
            } else sendMessage(message, contact.id, TYPE_TEXT) {
                binding.chatInputMessage.setText("")
            }
        }
    }

    private fun initInfoToolbar() {
        if (mReceivingUser.fullname.isEmpty()) {
            mToolbarInfo.findViewById<TextView>(R.id.toolbar_chat_fullname).text = contact.fullname
        } else mToolbarInfo.findViewById<TextView>(R.id.toolbar_chat_fullname).text =
            mReceivingUser.fullname

        mToolbarInfo.findViewById<ImageView>(R.id.toolbar_chat_image)
            .downloadAndSetImage(mReceivingUser.photoUrl)
        mToolbarInfo.findViewById<TextView>(R.id.toolbar_chat_status).text = mReceivingUser.state
    }

    override fun onPause() {
        super.onPause()
        mToolbarInfo.visibility = View.GONE
        mRefUser.removeEventListener(mListenerInfoToolbar)
        mRefMessages.removeEventListener(mMessagesListener)
        hideKeyboard()
    }
}
class UserOverlay(private val contact: User): Fragment(R.layout.fragment_user_account) {
    private lateinit var binding: FragmentUserAccountBinding
    private lateinit var mRefUserListener: AppValueEventListener
    private lateinit var mRefUserInfoListener: AppValueEventListener
    private val mRefUser: DatabaseReference = REF_DATABASE_ROOT.child(NODE_USERS).child(contact.id)

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentUserAccountBinding.inflate(inflater, container, false)
        val toolbar = binding.toolbar
        val appBarLayout = binding.appbar
        APP_ACTIVITY.mToolbar.visibility = View.GONE

        (APP_ACTIVITY as AppCompatActivity).setSupportActionBar(toolbar)
        (APP_ACTIVITY as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
        toolbar.setNavigationOnClickListener {
            activity?.onBackPressed()
        }

        appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener{ appBarLayout, verticalOffset ->
            val maxScroll = appBarLayout.totalScrollRange
            val percentage = abs(verticalOffset).toFloat() / maxScroll.toFloat()

            if (percentage >= 0.96) {
                binding.usernameImage.visibility = View.INVISIBLE
                binding.statusImage.visibility = View.INVISIBLE
                binding.profileImage.visibility = View.INVISIBLE

                toolbar.visibility = View.VISIBLE
                binding.profileImageSmallCollapsed.visibility = View.VISIBLE
                binding.userNameCollapsed.visibility = View.VISIBLE
                binding.statusCollapsed.visibility = View.VISIBLE
            } else {
                binding.usernameImage.visibility = View.VISIBLE
                binding.statusImage.visibility = View.VISIBLE
                binding.profileImage.visibility = View.VISIBLE

                toolbar.visibility = View.GONE
                binding.profileImageSmallCollapsed.visibility = View.INVISIBLE
                binding.userNameCollapsed.visibility = View.INVISIBLE
                binding.statusCollapsed.visibility = View.INVISIBLE
            }
        })

        return binding.root
    }

    override fun onResume() {
        super.onResume()
        initToolbar()
        initUserInfo()
    }

    private fun initUserInfo() {
        mRefUserListener = AppValueEventListener {
            val user = it.getUserModel()
            binding.usernameImage.text = user.fullname
            binding.statusImage.text = user.state
            binding.profileImage.downloadAndSetImage(user.photoUrl)

            binding.profileImageSmallCollapsed.downloadAndSetImage(user.photoUrl)
            binding.userNameCollapsed.text = user.fullname
            binding.statusCollapsed.text = user.state
        }
        mRefUserInfoListener = AppValueEventListener {
            val userInfo = it.getCommonModel()
            binding.userAccountBio.text = userInfo.bio
            binding.userAccountPhone.text = userInfo.phone
            binding.userAccountUsername.text = userInfo.username
            //remove button if no data
            if(binding.userAccountBio.text.isEmpty()) {
                binding.container2.visibility = View.GONE
            }
            if(binding.userAccountUsername.text.isEmpty()) {
                binding.container3.visibility = View.GONE
            }
        }
        mRefUser.addValueEventListener(mRefUserListener)
        mRefUser.addValueEventListener(mRefUserInfoListener)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val viewPager: ViewPager2 = binding.viewPager
        val tabLayout: TabLayout = binding.tabLayout
        val tabsPagerAdapter = TabsPagerAdapter(requireActivity(), contact)
        viewPager.adapter = tabsPagerAdapter

        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            when (position) {
                0 -> { tab.text = "Media"
                    val customView = LayoutInflater.from(tabLayout.context)
                        .inflate(R.layout.tab_layout, tabLayout, false)
                    val tabText: TextView = customView.findViewById(R.id.tabText)
                    tabText.text = "Media"
                    tab.customView = customView}
                1 -> { tab.text = "Voice"
                    val customView = LayoutInflater.from(tabLayout.context)
                        .inflate(R.layout.tab_layout, tabLayout, false)
                    val tabText: TextView = customView.findViewById(R.id.tabText)
                    tabText.text = "Voice"
                    tab.customView = customView
                }
            }
        }.attach()
    }

    private fun initToolbar() {
        binding.profileImageSmallCollapsed.downloadAndSetImage(contact.photoUrl)
        binding.userNameCollapsed.text = contact.fullname
        binding.statusCollapsed.text = contact.state
    }

    override fun onPause() {
        super.onPause()
        APP_ACTIVITY.mToolbar.visibility = View.VISIBLE
        mRefUser.removeEventListener(mRefUserListener)
        mRefUser.removeEventListener(mRefUserInfoListener)
    }
}

Replace fragment function (replace only dataContainer)

fun replaceFragment(fragment: Fragment, addStack: Boolean = true){
    if (addStack){
        APP_ACTIVITY.supportFragmentManager.beginTransaction()
            .addToBackStack(null)
            .replace(R.id.dataContainer, fragment).commit()
    } else {
        APP_ACTIVITY.supportFragmentManager.beginTransaction()
            .replace(R.id.dataContainer, fragment).commit()
    }
}

And activity_main.xml

<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/fullscreen_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/mainToolbar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="@style/mainToolbar">

        <include
            android:id="@+id/toolbar_info"
            android:visibility="gone"
            layout="@layout/toolbar_info"/>

    </androidx.appcompat.widget.Toolbar>


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:id="@+id/dataContainer"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/mainToolbar"
        android:layout_height="0dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

toolbar_info works correct when i press negative up, but it's not works with appBar Here screens when i return back from single chat fragment: SingleChat Fragment ContactsFragment (With it's own toolbar)

And when i return back from UserOverlay Fragment: UserOverlay Fragment with appbar instead toolbar Contacts Fragment (with mainToolbar instead it's own)

I try to include Appbar Layout to mainToolbar in activity_main.xml, but it's impossible because it need toolbar, not appBar Layout.

0 Answers0