After updating the Jetpack Compose library to beta01, I'm not able to show DialogFragment or BottomSheetDialogFragment.
Looks like ViewTreeLifecycleOwner cannot be found in my activity anymore. Also, I tried a couple of possible solutions but there are not any success.
The view in fragment is inflated with:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(
R.layout.fragment_full_screen_layout, container, false
).apply {
findViewById<ComposeView>(R.id.compose_view).setContent {...}
and dialog fragment is showing with:
DialogFragmentExample.newInstance().show(supportFragmentManager, null)
Does anyone have a similar problem? Please, any suggestion would be welcome.
Stacktracke:
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from DecorView#409dd5d[MainActivity]
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:214)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$LifecycleAware$1.createRecomposer(WindowRecomposer.android.kt:98)
at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:151)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:199)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:177)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:222)
I get the same exception without even using a DialogFragment.
My workaround is to set the ViewTreeLifecycleOwner from the Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ViewTreeLifecycleOwner.set(window.decorView, this)
// setContent(...); add fragment using Compose view
}
}
I'm currently using the following libraries:
androidx.activity:activity-ktx:1.3.0-alpha04
androidx.fragment:fragment-ktx:1.3.1
androidx.compose.*:*:1.0.0-beta02
As per the Fragment 1.3.1 release notes:
Dialogs within a DialogFragment can now get access to ViewTree owners through their DecorView, ensuring that DialogFragment can be used with ComposeView. (Ib9290, b/180691023)
So you should make sure you upgrade to Fragment 1.3.1:
implementation "androidx.fragment:fragment-ktx:1.3.1"
Related
I have a simple test function like this:
#Test
fun testfunc() {
val fragScenario = launchFragmentInContainer<MyFragment>()
}
launchFragmentInContainer provides me with this error:
Binary XML file line #10: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f040004 a=-1}
Caused by: java.lang.UnsupportedOperationException
which takes me to something wrong with the binding inflater.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
I assume view binding doesn't work with isolated fragments like this? Do I need to create an entire new fragment without view binding just for testing?
In the fragment testing documentation there is a note:
Note: Your fragment might require a theme that the test activity doesn't use by default. You can provide your own theme as an additional argument to launch() and launchInContainer().
This error normally occurs when there are style values that the activity isn't aware of. Be sure to pass your app or fragment style via the themeResId parameter.
I am implementing ViewBinding in one of my fragments. This fragment has a layout included like so:
...
<androidx.core.widget.NestedScrollView
android:id="#+id/sv_sudf_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/btn_sudf_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/eav_sudf_avatar">
<include
android:id="#+id/l_sudf_details"
layout="#layout/layout_sign_up_details_fields"/>
</androidx.core.widget.NestedScrollView>
...
I have followed this answer but it also does not work.
The generated view binding class for the fragment has the binding inside, however, the type for the attribute is View. When I then reference the View using binding.lSudfDetails the type is LayoutSignUpDetailFieldsBinding. Where this type is coming from I can't work out as there is no generated class with that name however, I would expect it would assign it the proper binding type. Here is the attribute in the FragmentSignUpDetailsBinding.java.
#NonNull
public final View lSudfDetails;
The bindings are all correctly setup however and it allow me to reference views within the nested layout but when I come to build I get unresolved reference errors. Lint does not complain when I reference them like this:
binding.lSudfDetails.etSudfDob
The compiler does fail however with errors such as this
Unresolved reference: etSudfDob
The binding itself is created according to the Android docs:
private var _binding : FragmentSignUpDetailsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentSignUpDetailsBinding.inflate(inflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvSudfWelcome.text = getString(R.string.sign_up_welcome,getString(R.string.app_name))
binding.lSudfDetails.etSudfDob.setOnClickListener {
showYearSelection()
}
}
The tvSudfWelcome binding works its the nested binding it doesn't like.
If you're using Android Studio 3.6.0 sometimes gradle plugin fails to generate ViewBinding fields for included layouts. Please update to Android Studio 3.6.1 and gradle plugin version to 3.6.1.
If someone has similar problem... I solved my problem with adding width and height for this included view. It helped, I do not have any idea why, but this would be my solution:
<include
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/l_sudf_details"
layout="#layout/layout_sign_up_details_fields"/>
If someone has a similar problem - I solved mine using:
execute gradle task : prepareKotlinBuildScriptModel
changing the id name to the same as the layout name worked for me.
For eg:
<include
android:id="#+id/layout_sign_up_details_fields"
layout="#layout/layout_sign_up_details_fields"/>
I was working with databinding instead of viewbinding though
I am developing a single activity application using the Android Jetpack navigation component. On one of the fragments I utilize the built-in data binding tools. Strangely enough, even though it was working just the week before, it just completely broke today for seemingly no reason.
The setup:
The fragment I use with binding has the following layout file:
<?xml version="1.0" encoding="utf-8"?>
<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>
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/example_layout_root"
tools:context=".example.ExampleFragment"
android:background="#color/main_1"
>
...
</ScrollView>
</layout>
I have stripped the main content but it shows that I have a <layout> element as the root with both the data and the fragment layout part defined.
The fragment code is the following:
class ExamleFragment : Fragment() {
private val viewModel: ExampleViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val binding : FragmentExampleBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_example, container, false)
// This one also does not work
// val binding = FragmentExampleBinding.inflate(inflater, container, false)
binding.vm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
I use Koin to inject the viewmodel into the fragment.
When I try to inflate the layout (either with DataBindingUtil or the generated FragmentExampleBinding class) it results in the following exception:
java.lang.IllegalStateException: FragmentExampleBin…flater, container, false) must not be null at
com.example.fragments.ExampleFragment.onCreateView(ExampleFragment.kt:38)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
at ...
And the stack goes straight down into OS territory so basically it doesn't give any useful information. I can't figure out why the result is null.
What I have tried:
I have tried inflating the layout with inflater.inflate(...) and it
works just fine but that way I can't use the data bindings.
I have tried removing EVERYTHING from the layout description that's
not absolutely neccessary and it still does not work.
I have tried reverting to previous commits (as far back as the first
working version with binding) and even though it used to work before,
it does not work now.
I even restarted and reset the emulator, tried different images, but
to no avail.
Has anyone encountered anything similar to this? The code really 'broke' over the weekend as I have just tested it the Friday before and it worked properly.
EDIT:
Okay, so I somehow managed to fix it.
I started experimenting with creating another fragment with data binding to see whether things are broken for all fragments or just that specific one. The new one seemed to work just fine but strangely, the old, broken fragment still refused to work even though they were basically the same.
So I did Clean + Rebuild (which I swear I performed as a first attempt to fix the issue) and it kind of just fixed it.
In my multi-module project, the reason for this problem was the fact that there was some kind of package name clash.
If you are getting this error without an apparent reason, double check the AndroidManifest.xml files of your data binding enabled modules.
Making sure all of the data binding enabled modules use unique package names solved the problem for me.
I spent hours to find the root of this problem hopefully others read this answer and won't spend that much.
Dont use DataBindingUtil for fragments, run your app once and android studio will generate a binding class for that fragment based off the name of your xml file, in your case the xml file is call fragment_example, so the class generated will be FragmentExampleBinding, then you call FragmentExampleBinding.inflate(....) like this:
val binding = FragmentExampleBinding.inflate(inflater, container, false);
Do it this way :
class ExamleFragment : Fragment() {
private val viewModel: ExampleViewModel by sharedViewModel()
lateinit binding:FragmentExmaple
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_example, container, false)
binding.vm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
please I need you to paste your error here
in my case I just forgot to add #AndroidEntryPoint to my fragment class
I'm using Android Studio 3.0 RC2 & Kotlin.
When I try to access a UI component the app crashes unless I first write findViewById. I thought Kotlin was supposed to get rid of having to write findViewById line? The UI is a fragment and I'm trying to access from the same fragment code. Is there a way to not have to write findViewById?
These lines work:
var userNameField = view?.findViewById<EditText>(R.id.userNameTextField) as EditText
userNameField.setText("hello world")
This line doesn't work without findViewById line
userNameTextField.setText("hello world")
I even have
import kotlinx.android.synthetic.main.fragment_sign_in.*
The onCreateView() code:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater!!.inflate(R.layout.fragment_sign_in, container, false)
var userNameField = view?.findViewById<EditText>(R.id.userNameTextField) as EditText
userNameField.setText("hello world")
return view
}
In the onCreateView just return the inflated view.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_sign_in, container, false)
}
In the onViewCreated you can access your view components
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
userNameField.setText("hello world")
}
I was brought here by searching for the same issue. I missed to add the kotlin-android-extensions to the build.gradle:
apply plugin: 'kotlin-android-extensions'
Was unable to access the ui components like OP faced looks like below was needed inside app gradle:
plugins {
...
id 'kotlin-android-extensions'
}
Even though after adding this line android studio was still not able to auto resolve the imports for kotlin synthetics so you may need to invalidate cache and restart.
If still not working then import manually depending on views
activity /fragment view: import
kotlinx.android.synthetic.main.<your_activity_view>.*
normal views :
import kotlinx.android.synthetic.main.<your_layout_view>.view.*
I faced a similar problem.
I had a private function. I'd also imported the kotlinx library. But I was not able to access the editText from that function.
I just removed the function and defined it again.
Voila! It worked.
For those who have this problem in 2021:
I know this question is asked in the past but since it is still relevant here I want to help others:
today I ran to this problem and tried the suggested ways and unfortunately, the
plugins { ... id 'kotlin-android-extensions' }
is not working anymore or more accurately is deprecated, so try to use the Jetpack view binding approach and you'll be good.
The message I got after adding the Gradle Kotlin Android extension dependency is:
The 'kotlin-android-extensions' Gradle plugin is deprecated. Please use this migration guide (click here) to start working with View Binding. (click here) and the 'kotlin-parcelize' plugin.
I am using Kotlin Android extensions in my project and I came across some behavior that I am unable to understand. I use this code to retain my fragment in the activity:
val fragment = fragmentManager.findFragmentByTag("hello") ?: HelloFragment()
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment, "hello")
.commit()
This is the retained Fragment:
import kotlinx.android.synthetic.hello.*
public class HelloFragment : Fragment() {
val text = "Hello world!"
override fun onCreate(savedInstanceState: Bundle?) {
super<Fragment>.onCreate(savedInstanceState)
setRetainInstance(true)
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.hello, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super<Fragment>.onViewCreated(view, savedInstanceState)
text_view.setText(text) // <- does not work when retained
}
}
and its XML layout hello.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" />
Everything works as expected – the text_view.setText() displays Hello world! on screen on the first launch. But when you rotate the screen the text_view.setText() does not work. This is weird because text_view is not nullable and has to reffer to some view. If you remove setRetainInstance(true) and leave the fragment recreate every time this problem disappears. Any thoughts what might cause this problem?
UPD: The issue is fixed now. You don't have to call clearFindViewByIdCache() manually anymore.
View cache is not cleared after calling onDestroyView(). There is an open issue.
For now, you can explicitly invoke clearFindViewByIdCache() in onDestroyView() to clear the cache. This method is part of the synthetic package so you have to import it
import kotlinx.android.synthetic.*
I have found the answer myself. The Fragment class does not inflate the layout directly – it has property view: View? which holds it. This should be pretty obvious since it is created with onCreateView. In order to access the properties within the view you have to set the import
import kotlinx.android.synthetic.hello.view.*
and then access the properties as follows
view?.text_view?.setText(text)
Note that these properties are nullable.
Just to clarify. The issue is fixed now. You don't have to pass clearFindViewByIdCache() anylonger. Please see issue tracker: https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-8073