I have an issue on my app I am trying to solve it from 2 days now but I can't find anything about it.
Almost every single button in a fragment which is added dynamically at my Activity onCreate are not clickable for some users. Nobody in my team experiences the issue so, I reached a user with the bug and made some test with him and here are the results:
If he clicks just once the button doesn't work but if he spams it, the button sometimes trigger.
I have also placed some debugging in his version and this is what I found:
user_hud_main_button.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> report += "down;"
MotionEvent.ACTION_UP -> report += "up;"
}
false
}
user_hud_main_button.setOnClickListener {
report += "click;"
toggleMainButton()
Analytics.send(Event.Type.BtnAddMain)
}
edit: the setOnTouchListener has been added only for debug purpose while working on the problem
while on my test phones I get:
down;up;click;down;up;click;down;up;click;
the user get:
down;up;down;up;down;up;down;up;
The up and down events are correctly sent but not the onclick. Why does this happen and why only for few users ?
Here is the layout of the activity:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:name="com.hulab.mapstr.newArch.MapFragment"
android:id="#+id/activity_map_fragment_map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
...
<FrameLayout
android:id="#+id/activity_map_hud_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<!-- Bottom drawer -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/main_bottom_sheet"></include>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
activity code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
supportFragmentManager.beginTransaction().add(R.id.activity_map_hud_container, UserHUDFragment()).commit()
...
}
UserHUDFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
user_hud_main_button.setOnClickListener {
toggleMainButton()
Analytics.send(Event.Type.BtnAddMain)
}
...
}
UserHUD layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/user_hud">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/user_hud_current_map_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/user_hud_main_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:elevation="1dp"
app:fabSize="normal"
app:useCompatPadding="true"
android:src="#drawable/add_fab_button" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Thanks in advance and sorry for my english
It is not allowed to have both onTouch and onClick listener on one view. You can do everything with onTouchListener.
Ok the problem was that I had a "infinite loop" from my activity asking for a permission when users declined it with "don't ask again" option, so every click event was broken
Related
I want to leverage ViewStub to optionally show a portion of UI. When the root of my inflated item is ConstraintLayout then it doesn't render.
But if that root is MaterialCardView then it displays as intended.
<!-- my_fragment.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"
android:id="#+id/fragmentRootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="#+id/fragmentViewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/fragmentViewStub"
android:layout="#layout/item"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- item.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"
android:id="#+id/itemRootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/itemLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/itemLabelText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
/* MyFragment.kt */
#AndroidEntryPoint
class MyFragment : Fragment(R.layout.my_fragment) {
private val viewModel: MyViewModel by viewModels()
private val viewBinding: MyFragmentBinding by viewLifecycleLazy {
MyFragmentBinding.bind(requireView())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.fragmentViewStub.inflate()
}
}
I've tried matching the ViewStub's id/inflatedId attributes and programmatically setting dimensions/constraints but neither solved the issue.
What am I overlooking about making these pieces work together?
Forgot to update with my answer/oversight. This worked as expected in both cases all along.
Nightmode was enabled and the behavior of MaterialCardView has automatic contrast between its card and content. Meanwhile, when ConstraintLayout inverts its theme the label appears to be missing but is indeed present, as white text on white background.
I have an Activity with a TabLayout, a ViewPager2 and a FragmentContainerView like this:
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="#style/AppTheme"
tools:context="com.safirecctv.easyview.activities.main.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/visiotech_red"
app:popupTheme="#style/AppTheme" />
<com.google.android.material.tabs.TabLayout
android:id="#+id/activity_main_tab_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/visiotech_red"
app:tabIconTint="?attr/tabLayoutTabColor"
app:tabIndicatorColor="?attr/colorSecondary" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/activity_main_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/main_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/app_bar" />
</RelativeLayout>
When I first launch the Activity with its Fragment (I've reduced it to one tab and one fragment for the sake of simplicity), everything works as expected, the Fragment takes the whole screen. But when I come back from another Activity by calling:
mActivity?.finish()
The fragment loads well, but only takes half of the screen height. I've tried to inspect with Layout Inspector, but if I attach it before coming back, the app force closes, and if I attach it after (when the Fragment is loaded and only taking half of the screen height), then it recreates and works well again.
I am testing in a real device, not in the emulator.
This is my Fragment XML:
<layout 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">
<data>
<variable name="viewModel" type="com.safirecctv.easyview.viewmodels.main.DeviceManagementViewModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:orientation="vertical"
android:id="#+id/list_view_devices"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/listViewBackgroundColor"
app:data="#{viewModel.devices}" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:src="#drawable/icon_plus"
app:backgroundTint="#color/visiotech_red"
app:tint="?attr/buttonTextColor"
tools:ignore="ContentDescription" />
</RelativeLayout>
</layout>
And this is the code behind:
package com.safirecctv.easyview.activities.add_edit_device.fragments
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.safirecctv.easyview.R
import com.safirecctv.easyview.databinding.ActivityAddEditDeviceIpddnsFragmentBinding
import com.safirecctv.easyview.viewmodels.add_edit_device.IpDdnsDeviceViewModel
import dagger.hilt.android.AndroidEntryPoint
/**
* Fragment used to add or edit IP/DDNS devices to/from the database.
* As of June, 2021, the following vendors support IP/DDNS login: Safire/Hikvision, XSecurity/Dahua, Uniview.
*/
#AndroidEntryPoint
class AddEditIPDDNSDeviceFragment : Fragment(R.layout.activity_add_edit_device_ipddns_fragment) {
private val mViewModel: IpDdnsDeviceViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = ActivityAddEditDeviceIpddnsFragmentBinding.bind(view)
binding.apply {
btnSaveDeviceIpDdns.setOnClickListener { trySaveDevice() }
viewModel = mViewModel
lifecycleOwner = viewLifecycleOwner
}
}
//endregion
//region Device methods
private fun trySaveDevice() {
//mViewModel.trySaveDevice()
activity?.finish()
}
//endregion
}
Could it be related to the fact that I'm not calling onCreateView in the Fragment but instead delegating it to the constructor?
Thank you very much.
EDIT: I could open Layout Inspector finally, and this is what it shows: RecyclerViewImpl is taking the whole space but the FrameLayout inside it only takes like half of the screen:
I finally found it. The problem was in the Activity's XML. Everything was inside AppBarLayout and that was causing the issue. I just left the MaterialToolbar inside AppBarLayout and the rest as a sibling, as explained in this post (in Spanish). Then everything started working as intended.
See:
var isShimmerCalled: Boolean = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentProductBinding.bind(view)
setupRecyclerView()
if (!isShimmerCalled) {
Log.d("Main", "Called shimmer")
startShimmerLayout()
isShimmerCalled = true
}
setUpView()
getProductData()
}
So, I'm working with viewmodel and livedata, and as you see above, if isShimmerCalled == false then I'm printing log "Called Shimmer" and then my shimmer layout is displaying in UI. Fine.
D: setupRecyclerView
D: Called shimmer
Now I switch to next fragment (I've single activity with multiple fragment using navigation component) and then pressed back button
D: setupRecyclerView
So, notice here, only setupRecyclerView is printed because of that flag. that's correct.
Then my question is why Shimmer layout is displayed in UI?
Edit:
Complete code: https://drive.google.com/file/d/1rnx1etUjGsqlvt1Bxy-SPxTvvUDufash/view?usp=sharing
Should I do like this (it's not my code, someone else project fragment, because in that person code this problem not happening):
viewModel.breakingNews.observe(viewLifecycleOwner, Observer { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
response.data?.let { newsResponse ->
newsAdapter.differ.submitList(newsResponse.articles)
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Log.e(TAG, "An error occured: $message")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
**EDIT:**Oh my gosh!! Even though I commented startShimmerLayout() still shimmer is showing
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/parentlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.products.ProductFragment">
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/shimmer_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<!-- Adding 3 rows of placeholders -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
<include layout="#layout/placeholder_layout" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<TextView
android:id="#+id/text_view_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#id/button_retry"
android:layout_centerHorizontal="true"
android:text="Results could not be loaded"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="#+id/button_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Retry"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
onCreateView() is the Fragment equivalent of onCreate() for Activities and runs during the View creation.
onViewCreated() runs after the View has been created.
should I use one over the other for performance? NO. There's no evidence of a performance boost.
There is actually an onCreate() method in Fragments too, but it's rarely used (I do never use it, nor find a good use case for it).
I always use onCreateView() in Fragments
try placing this code in onCreateView instead
I'm using the "new" Android Jetpack navigation, instead of relying on FragmentManager. I've got a simple main layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<FrameLayout
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/bottomNav">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/navigation_app" />
</FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/bottom_menu"
app:layout_constraintBottom_toBottomOf="#+id/root"/>
</android.support.constraint.ConstraintLayout>
And two additional layouts: "Splashscreen" with a TextView, "Login" with a button. The splashscreen fragment is used as starting point, and has a listener defined as follows:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
textView.setOnClickListener{
findNavController(nav_host_fragment).navigate(R.id.action_splashScreenFragment_to_loginFragment)
}
}
screenshot
Instead of replacing TextView fragment with Button fragment, the Button fragment is stacked on top.
I've only encountered workarounds such as setting a background color, and clickable parameter, which I consider to be more of a hack, as I believe that the behaviour is exactly the same, but the fragment below is simply hidden. How to properly switch fragments using the NavController?
Issue seems to have occured because of these two lines:
val host = NavHostFragment.create(R.navigation.navigation_register_login)
supportFragmentManager.beginTransaction().replace(R.id.nav_host_fragment,host).setPrimaryNavigationFragment(host).commit()
I guess they shouldn't be called, and all basic requirements are fulfilled by this line in layout:
android:name="androidx.navigation.fragment.NavHostFragment"
You need to use popUpTo and popUpToInclusive to clear the backstack.
Check the Android Developers docs
I have drawer layout, where I need to remove AppBar shadow for particular page (fragment).
For this I am using this code
fun setToolbarShadow(dropShadow: Boolean) {
if (dropShadow) {
ViewCompat.setElevation(appBar, Utils.dp2px(4, resources))
} else {
ViewCompat.setElevation(appBar, 0f)
}
}
and it works well for others than the first fragment.
I tried put it inside onCreate of activity, onStart, onCreateView and onViewCreated of fragment, but nothing works.
How to set it properly after activity and fragment are created?
EDIT:
I made simple hello world app with one activity to try it, here is the code:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cz.svobodaf.myapplication.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="#+id/top_nav_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</android.support.design.widget.AppBarLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
ViewCompat.setElevation(appBar, 0f)
}
override fun onResume() {
super.onResume()
ViewCompat.setElevation(appBar, 0f)
}
}
This also doesn't work after start, but when I lock device and unlock again, onResume is called and suddenly it works.
I need to know ehere to put the code to set elevation after app start.
I found solution by setting AppBar outlines to null.
appBar.outlineProvider = null
But I couldn't figure out why elevation doesn't work. It looks like elevation is set after all starting methods are called -.-