Generated FragmentBinding does not contain my CustomView id - android

I'm using databinding in my android project and my dashboard_fragment_layout.xml contain LinearLayout which contain TextView and CustomView:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.example.ui.dashboard.DashboardFragment">
<data>
<variable
name="ViewModel"
type="com.example.ui.dashboard.DashboardViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tvSome"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<view
android:id="#+id/viewCustom"
class="com.example.ui.dashboard.CustomView"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="bottom|center_horizontal" />
</LinearLayout>
</layout>
When i trying to access my custom view via generated FragmentDashboardBinding:
mCustomView = mBinding.viewCustom;
i'm getting 'Can't resolve symbol viewCustom' in AndroidStudio. And i don't have this problem with TextView, it's accessible from mBinding object:
mSomeTextView = mBinding.tvSome; // all fine, no errors
I'm always getting this error with custom views, and the only way to access my custom view object is to do this by old way with findViewById:
mCustomView = view.findViewById(R.id.viewCustom); // that works
All together:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewDataBinding = DataBindingUtil.inflate(inflater, fragment_dashboard_layout, container, false);
view = mViewDataBinding.getRoot();
mCustomView = mViewDataBinding.viewCustom; // 'Can't resolve symbol viewCustom
mTextView = mViewDataBinding.tvSome; // all fine
mCustomView = view.findViewById(R.id.viewCustom); // that works
}
How can i access custom views via generated databinding object ?

Change to com.example.ui.dashboard.CustomView in xml code .
And remove the class in xml code .
Try this .
<com.example.ui.dashboard.CustomView
android:id="#+id/viewCustom"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="bottom|center_horizontal" />

You have used wrong view tag. try replacing view tag to View
Replace your view code to this.
<View
android:id="#+id/viewCustom"
class="com.example.ui.dashboard.CustomView"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="bottom|center_horizontal" />
Happy coding!!

Related

View Binding - How do I get a binding for included layouts?

While working with view binding, I came across a couple of undocumented cases.
First: How do I get binding for included view layout parts? The main binding only sees items defined in the main layout.
Second: How do I get binding for merged layout parts. Again, the main binding only sees items in the main layout?
In case of:
Include with generic layout (not merge node), we need to assign ID to included part, this way in binding we will have access to included sub part
<include
android:id="#+id/your_id"
layout="#layout/some_layout" />
This way in your activity code:
private lateinit var exampleBinding: ActivityExampleBinding //activity_example.xml layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
setContentView(exampleBinding.root)
//we will be able to access included layouts view like this
val includedView: View = exampleBinding.yourId.idOfIncludedView
//[...]
}
Include with merge block in external layout. We can't add ID to it because merge block is not a view.
Let's say we have such eternal merge layout (merge_layout.xm):
<?xml version="1.0" encoding="utf-8"?>
<merge 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"
tools:showIn="#layout/activity_example">
<TextView
android:id="#+id/some_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World" />
</merge>
To properly bind such merge layout we need to:
In your activity code:
private lateinit var exampleBinding: ActivityExampleBinding //activity_example.xml layout
private lateinit var mergeBinding: MergeLayoutBinding //merge_layout.xml layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
//we need to bind the root layout with our binder for external layout
mergeBinding = MergeLayoutBinding.bind(exampleBinding.root)
setContentView(exampleBinding.root)
//we will be able to access included in merge layout views like this
val mergedView: View = mergeBinding.someView
//[...]
}
Regarding your first question, you can get a view binding for the included layout.
Here is a sample main_fragment.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
</LinearLayout>
And MainFragment.java can be like this:
public class MainFragment extends Fragment {
private MainFragmentBinding binding;
private ToolbarBinding toolbarBinding;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = MainFragmentBinding.inflate(inflater, container, false);
toolbarBinding = binding.toolbar;
return binding.getRoot();
}
#Override
public void onDestroy() {
super.onDestroy();
toolbarBinding = null;
binding = null;
}
}
Now you have two bindings: One corresponds to the main layout and the other corresponds to the included layout.
If you want to bind included layout then,
For Activity
YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(getLayoutInflater);
View view = mainLayoutBinding.getRoot();
YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);
For Fragment
YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(inflater,container,false);
View view = mainLayoutBinding.getRoot();
YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);
Make Sure if your main layout binding parent root is LinearLayout then,
includedLayoutBinding parent layout also be a linear layout
Suppose I included a layout in my activity_main.xml file as follows:
<include
android:id="#+id/ll_layout1"
layout="#layout/layout1"
android:visibility="gone" />
And suppose I wanted to change its visibility. I can do so as follows:
activityMainBinding.llLayout1.root.visibility = View.VISIBLE
In my case, I forgot to assign id to the include tag
Now, when you've assigned the id, you'll be able to get the binding object as,
YourMainLayoutBinding.YourIncludeTagIDLayoutBinding
Follow steps:-
private val binding : FragmentBinding
by viewBinding(FragmentBinding::bind)
make sure to do the following in "onViewCreated(view: View, savedInstanceState: Bundle?)"
val binding2 = binding.root.include_layout_id
e.g. val binding2 = binding.root.tool_bar_layout
Now access your include layout, views here. e.g.
binding2.textView.text = "your text"
Using the data binding library. Then wrap your XML layout with <layout> tag
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
...
</LinearLayout>
</layout>
toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/ivImage"
... />
<TextView
android:id="#+id/tvTitle"
... />
</LinearLayout>
MainActivity.kt
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// Access include layout views
binding.toolbar.rootView.ivImage.setImageResource(R.drawable.ic_back_arrow)
binding.toolbar.rootView.tvTitle.text = getString(R.string.home)
...
}
In the include layout you must create a Container layout and put here the id.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Exception when using data binding in fragment: "The specified child already has a parent. You must call removeView() on the child's parent first"

Android Studio 3.1, java 1.8
I try to use data binding:
Here settings.xml layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="handler"
type="com.myproject.SettingsFragment" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:id="#+id/contactUsContainer"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="#{handler::onClickContactUs}">
<ImageView
android:id="#+id/contactUsImageView"
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#drawable/ic_settings_contacts_us"
<TextView
android:id="#+id/contactUsTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/contact_us"/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</FrameLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
</layout>
Here fragment SettingsFragment.java :
public class SettingsFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
SettingsBinding binding = DataBindingUtil.setContentView(getActivity(), R.layout.settings);
binding.setHandler(this);
return binding.getRoot();
}
public void onClickContactUs(View view) {
}
}
But I get error:
FATAL EXCEPTION: main
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:3337)
at android.view.ViewGroup.addView(ViewGroup.java:3208)
at android.view.ViewGroup.addView(ViewGroup.java:3165)
at android.view.ViewGroup.addView(ViewGroup.java:3145)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1425)
android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
DataBindingUtil.setContentView() should be use for binding in an Activity because the method will set the content view for the Activity(like you would normally do with the setContentView() method). This is why you get the exception that the view(binding.getRoot()) is already appended to a parent view.
In your fragment use DataBindingUtil.inflate() to inflate the layout and create the binding without actually appending the view(which the fragment does itself later):
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
SettingsBinding binding = DataBindingUtil.inflate(inflater, R.layout.settings, container, false);
binding.setHandler(this);
return binding.getRoot();
}

How to apply inbuilt or custom theme to fragment within an activity?

My problem is to make a fragment full screen and apply custom theme on it. I tried this way but unable to get result. RssDetailViewFragment.cs consists following lines of code:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
// create ContextThemeWrapper from the original Activity Context with the custom theme
Context contextThemeWrapper = new ContextThemeWrapper(Activity, Android.Resource.Style.ThemeLightNoTitleBarFullScreen);
// clone the inflater using the ContextThemeWrapper
LayoutInflater localInflater = inflater.CloneInContext(contextThemeWrapper);
rootView = localInflater.Inflate(Resource.Layout.RSSFeedDetails, null, false);
// inflate progressbar
progressBar = rootView.FindViewById<ProgressBar>(Resource.Id.progress_bar);
progressBar.Visibility = ViewStates.Visible;
// setting control toolbar
Toolbar toolbar = rootView.FindViewById<Toolbar>(Resource.Id.top_control_toolbar);
((AppCompatActivity)this.Activity).SetSupportActionBar(toolbar);
// setting icon on toolbar
toolbar.SetNavigationIcon(Resource.Drawable.ic_back20);
toolbar.SetBackgroundColor(Resources.GetColor(Resource.Color.primary));
View navigationIcon = toolbar.GetChildAt(1); //NavigationIcon
and my xml file for RssFeedDetals is
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="60dp"
android:orientation="vertical"
android:background="#color/viewBackground">
<include
android:id="#+id/top_control_toolbar"
layout="#layout/TopControlToolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/viewBackground">
<WebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</ScrollView>
<ProgressBar
android:id="#+id/progress_bar"
style="#android:style/Widget.DeviceDefault.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible" />
</LinearLayout>
Main thing to ask this question is to make the fragment fullscreen on Min Sdk 15 plus.
Thank you.
I want to hide the toolbar of activity and statusBar of app and cover the area with fragment view.
When you show your RssDetailViewFragment you could hide the Statusbar and Toolbar like this :
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
SetHasOptionsMenu(true);
//Hide the StatusBar
WindowManagerLayoutParams attr = Activity.Window.Attributes;
attr.Flags = WindowManagerFlags.Fullscreen;
Activity.Window.Attributes = attr;
//Hide the ToolBar
ftoolbar = (V7Toolbar)Activity.FindViewById<V7Toolbar>(Resource.Id.toolbar);
ftoolbar.Visibility = ViewStates.Gone;
//Load views for this Fragment
View view = LayoutInflater.From(Activity).Inflate(Resource.Layout.MGradesView, null);
return view;
}

SwipeRefreshLayout loading circle is not hiding in Android

I am developing an Android app. I am using listview together with SwipefreshLayout. But when I refresh list view using SwipefreshLayout , the loading circle is never hidden back. I am using it in fragment.
This is layout file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- place your view here -->
<ListView
android:dividerHeight="#dimen/list_item_divider_height"
android:padding="#dimen/list_padding"
android:divider="#color/whitesmoke"
android:id="#+id/listview_podcast_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"></ListView>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:layout_below="#+id/swipe_refresh_layout"
android:textSize="17dp"
android:textStyle="bold"
android:textColor="#color/black"
android:textAlignment="center"
android:id="#+id/tf_no_records"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
This is how I am using it in fragment
public class PodcastListFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
// property variables here
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
.
.
.
refresher = (SwipeRefreshLayout)view.findViewById(R.id.swipe_refresh_layout);
refresher.setOnRefreshListener(this);
return view;
}
#Override
public void onRefresh() {
refreshListView();
}
}
.
.
.
}
The loading circle is never get hidden back as in screenshot below
What is wrong with my code and how can I solve it?
You have to set refreshing to false after refresh
refresher.setRefreshing(false);
From doc
Update:
Kotlin:
refresher.isRefreshing = false

Can two Android Fragments use the same ids?

I want to have two or more Fragments which will be processed similarly. Can I reuse ids in each Fragment?
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
<Fragment
android:id="#+id/fragment_1"
class="com.example.DoesSomethingToTitle"
<TextView android:id="#+id/title" />
</Fragment>
<Fragment
android:id="#+id/fragment_2"
class="com.example.DoesSomethingToTitle"
<TextView android:id="#id/title" /> <!-- same id -->
</Fragment>
</FrameLayout>
And use it like this:
public class DoesSomethingToTitle {
private String titletext;
public DoesSomethingToTitle(String titletext) {
this.titletext = titletext;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.feed, container, false);
// find the title TextView and mutate it
((TextView) v.findViewById(R.id.title)).setText(titletext);
return v;
}
}
(I realize for something as simple as this, I could just specify the title string in the XML; my actual usage is more complex.)
Can I reuse ids in each Fragment?
Yes you can.

Categories

Resources