I have seen 2 approaches for setting Visibility in XML using Databinding
<variable
name="vm"
type="com.example.myapp.viewmodel.MyViewmodel" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
style="#style/ProgressBarMediumWhite"
android:visibility="#{vm.showLoader ? View.VISIBLE : View.GONE}" />
<variable
name="vm"
type="com.example.myapp.viewmodel.MyViewmodel" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
style="#style/ProgressBarMediumWhite"
android:visibility="#{vm.showLoader}" />
I want to know which one is the better approach or both seems to be good.
How bad it is to Import android view in to the XML file.
The first one is the better.
Checks if showLoader is true then sets the views visibility to visible else gone.
The second one is passing a boolean showLoader as the view's visibility which should end up throwing an error.
Both approaches are the same, there is no difference.
However, there is one more way to create binding adapter and use it in xml layout files for changing visibility.
For example:
Create BindingAdapters.kt file and put this code in it
#BindingAdapter("goneUnless")
fun goneUnless(view: View, visible: Boolean) {
view.visibility = if (visible) VISIBLE else GONE
}
Now you can use this binding adapter like this
<ProgressBar
style="#style/ProgressBarMediumWhite"
app:goneUnless="#{vm.showLoader}" />
In most cases, both approaches work just fine.
However, when you have to make a choice like this, always choose the option that does not involve passing a view as a parameter. Unexpected events from the source (where the parameter comes from) might cause your app to misbehave or cause the view to render wrongly.
Therefore, the first approach seems to be more reliable.
android:visibility="#{vm.showLoader ? View.VISIBLE : View.GONE}"
Related
I'm seeing this strange behavior and couldn't find anything similar to this.
So I have a parent Activity and inside is a Fragment, which I'm including in parent via include element and then in parent's onCreate, create Fragment and replace it with this include layout (Tell me if this is a right way? I was using FrameLayout but then switched to include and defined an id to it).
Activity
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.sourcey.materiallogindemo.CustomerDetailActivity"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/app_bar"
app:layout_constraintBottom_toTopOf="#id/layout_bottom_bar"
layout="#layout/fragment_customer_detail" />
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomappbar.BottomAppBar>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment
<androidx.constraintlayout.widget.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"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- THIS IS THE CULPRIT -->
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_update_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/sku_list"
app:layoutManager="LinearLayoutManager"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
tools:listitem="#layout/fragment_s_k_u_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment is inflated correctly but when I do this inside onCreateView
rootView.btn_update_position.setOnClickListener {
// ... log something
}
and press the Button, it doesn't do anything? Even though most findings were led to this suggestion that I should inflate the view and then set onClickListener.
I also tried doing these
rootView.findViewById<MaterialButton>(R.id.btn_update_position).setOnClickListener {
// ... log something
}
and
val button = rootView.findViewById<MaterialButton>(R.id.btn_update_position)
button.setOnClickListener {
// ... log something
}
but none of them works.
I also tried above approaches in onViewCreated to see if maybe I was not getting the reference but no errors were thrown and no reaction was coming.
Only thing that works is this
activity?.findViewById<MaterialButton>(R.id.btn_update_position)
?.setOnClickListener {
// ... log something
}
I'm trying to understand why this happens? Could this be the issue of using include the Fragment?
NOTE I'm not a pro in android just do hobby work in it so don't know very deeply about it.
EDIT As you can see I have a RecyclerView in Fragment layout, I'm inflating the layout and then setting its adapter items which seems to work fine opposed to button.
rootView.sku_list.adapter = Adapter()
I'm bit confused about what you want to do here
First,<include> doesn't create new view, it just include the xml into the parent xml file so basically it still on activity and you need activity to findViewById
Second, about your question what different between FrameLayout and <include>.
With <include> like i said above, it just add xml file to the parent file, the main usage is for re-use layout (you can include it anywhere) .
With FrameLayout, from official doc : "FrameLayout is designed to block out an area on the screen to display a single item". E.g : you want your layout have a header and footer for all screen, only the middle part change so place a frame layout at middle then load different view for each screen, because that flexibility frame layout usually use for display fragment (you can google how to use frame layout for more details)
For some reason BottomNavigationView has a visual bug in layout. Does anyone know any way to fix it? The problem resolves after any button is clicked or after I minimize app and restore it.
This is how it is supposed to look:
Everything works when menu is inflated via XML.
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="56dp"
...
app:menu="#menu/bottom_navigation_4_game" />
When I added MenuItem programmatically:
navigationView.menu.clear()
navigationView.inflateMenu(R.menu.bottom_navigation4)
We may see in LayoutInspector, that there are actually 5 items, but two of them are overlayed and not seen:
The problem is probably in BottomNavigationMenuView. In LayoutInspector getWidth() returns 0. Invalidating views didn't help.
If you are trying to create dynamic BottomNavigationView with 2 different menu items set,
So instead of dynamically adding the menu item, use 2 different xml layouts (which have define 2 different app:menu property) and based on the conditions switch between them in your code then.
So, XML would look like this:
<BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_1" />
<BottomNavigationView
android:id="#+id/bottom_navigation_mini_player"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_2" />
I found a weird code block in the app, that was to blame. Turns out, that TransitionManager didn't end its transitions with ConstraintLayout. This code: updateConstraints {} was called immediately after dynamically changing BottomNavigationView, hence its child views transition was interrupted, I guess.
private fun updateConstraints(f: ConstraintSet.() -> Unit) {
TransitionManager.beginDelayedTransition(root)
val set = ConstraintSet().apply { clone(root) }
set.f()
set.applyTo(root)
}
Problem:
It seems when I have a specific combination of views and setVisibility between Visible and Gone the RecyclerView does not update properly after initial load. I have a RelativeLayout->ConstraintLayout->Constraint Group(with visibility dynamically set). The view within the constraint group is not updating properly.
Example Use Case of Problem:
So the linked code below it will show a search view at the top. The initial state of empty shows the view properly(With the search icon showing). Then if you type "T" then the search icon will disappear(it shouldn't). So if you either delete the T or type the next letter "E" then it shows again. Also if you delete all search text and type T again it will show.
View Code:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.davidcorrado.myapplication.PlayerVM" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/playerCell_main_layout"
android:layout_width="match_parent"
android:layout_height="84dp"
android:layout_alignParentTop="true">
<android.support.constraint.Group
android:id="#+id/playerCell_status_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{vm.showingStatusIcons}"
app:constraint_referenced_ids="playerCell_lineup_status_image" />
<ImageView
android:id="#+id/playerCell_lineup_status_image"
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"
app:layout_constraintTop_toTopOf="parent"
android:src="#drawable/ic_search" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
</layout>
Quirks that might help:
1) If I move the visibility line to the relativeLayout or the ConstraintLayout the things seem to work
2) If I remove the RelativeLayout view things seem to work.
See the below for the full code example:
https://github.com/DavidCorrado/LayoutIssue
As far as I can tell, the reason the search icon disappears is due to the call to ObservableList.clear() then ObservableList.addAll(*)
So the disappear happens on the clear, then the reappear happens on the addAll.
As the callbacks in the adapter kick in on both calls, my hunch is it animates the view to disappear, then animates to show when the addAll is triggered.
I have verified this by not actioning a 'List.clear()' and instead in your textChange listener simply adding more views to the list.
I'm not sure how you would clear and add items to the list without animating the clear, perhaps there are some settings you can toggle in the RecyclerAdapter to ignore the remove of Views or not animate the entry of Views?
My adjusted code in your callback for class PlayersListVM
class PlayersListVM {
val filteredVms: ObservableList<ViewModel> = ObservableArrayList<ViewModel>()
val showing = PlayerVM(isShowing = true)
val missing = PlayerVM()
init {
filteredVms.add(showing)
}
fun onQueryChanged(newText: String) {
if (filteredVms.size % 2 == 1) filteredVms.add(missing)
else filteredVms.add(showing)
}
}
I show items in recyclerview and use databinding. In xml layout I has such view:
<include
android:visibility="#{viewmodel.expandable ? View.VISIBLE : View.GONE}"
bind:viewmodel="#{viewmodel}"
layout="#layout/full_station_layout"/>
It works well but I has one issue: while recyclerview initializing and bind items to views this layout flashes once on the screen although initial value viewmodel.expandable is false. So, I decided temporary hide this layout and tried using default-parameter in xml like this:
<include
android:visibility="#{viewmodel.expandable ? View.VISIBLE : View.GONE, default=View.GONE}"
bind:viewmodel="#{viewmodel}"
layout="#layout/full_station_layout"/>
But something went wrong:
error: 'View' is incompatible with attribute android:visibility (attr) enum [gone=2, invisible=1, visible=0].
So, or I incorrectly use this parameter or Google remove this keyword from xml databinding rules (I've seen example of usage default-keyword in xml on Google developers before, but now I couldn't)
You can set gone, visible, invisible in default property. Replace with below.
<include
android:visibility="#{viewmodel.expandable ? View.VISIBLE : View.GONE, default=gone}"
bind:viewmodel="#{viewmodel}"
layout="#layout/full_station_layout"/>
Check if you have already imported the View class.
<data>
<import type="android.view.View"/>
<variable ..... />
</data>
Also, the default correct syntax for default value for visibility is default=gone, no default=View.GONE
I'm wondering if it's possible to use DataBinding to do conditionally show a layout based on a methods boolean response. Here's what I'm trying to do
XML Layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="View"
type="android.view.View"/>
<variable
name="App"
type="com.app.JTApp"/>
</data>
<include layout="#layout/view_checkout_total_cardview"
android:visibility="#{App.isLandscape() ? View.GONE : View.VISIBLE}" />
</layout>
JTApp class:
public class JTApp {
public boolean isLandscape() {
Timber.d("putty-- isLandscape: --------------------------");
return getResources().getBoolean(R.bool.is_landscape);
}
…
}
Currently this doesn't work. Am I missing something or is this not possible? I'm coming from the web where this is possible with frameworks like Angular.
Yes, using a conditional statement within XML is possible. I am not too familiar with data binding library, but a similar functionality is used in the documentation:
Zero or more import elements may be used inside the data element.
These allow easy reference to classes inside your layout file, just
like in Java.
<data>
<import type="android.view.View"/>
</data>
Now, View may be used within your binding expression:
<TextView
android:text="#{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{user.isAdult ? View.VISIBLE : View.GONE}"/>
I believe the only issue with your code is that you are using the View as a variable instead of as an import in your <data> element.
You can do this way easier with resource-modifiers:
have one layout in layout-land that does not contain the cardview
have another layout in layout that does contain the cardview
so you will get this effect and in landscape it is not even inflated and then set invisible