this Fragment is not captured - android

This is not captured once I popBackStack after setting data on the savedStateHandler
Error 'this#UserFragment' is not captured
UserFragment (Receiver)
class UserFragment: Fragment() {
private var users: ArrayList<User>? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<User>("data")?.observe(viewLifecycleOwner, Observer { data ->
users?.add(data) // error 'this#UserFragment' is not captured
})
}
}
SecondFragment (Sender)
Setting data on the SecondFragment
findNavController().previousBackStackEntry?.savedStateHandle?.set("data", data)
findNavController().popBackStack()
It used to work fine until I upgraded. build.gradle(app)
defaultConfig {
applicationId "com.app.test"
minSdk 33
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Related

TextField breaks composing when used with Jetpack navigation?

I’ve been trying to put a simple app together, using Compose 1.0.0-beta09, Kotlin 1.5.10, and Jetpack Navigation 2.3.4.
The app has one activity and two fragments.
First (main) fragment /screen (clicking in the button takes me to the second fragment/screen ): Screen One screenshot
Second fragment:/screen: Screen Two screenshot
Problem: After interacting with (putting the cursor in) the TextField on the first screen and subsequently clicking on the button, the second screen loads but is empty (the onCreateView of the SecondFragment is called but the setContent doesn’t work / the screen doesn’t get recomposed?).
If I don’t interact with the TextField, the problem doesn’t happen.
I’ve tested on emulators with API levels 28 & 30, compose 1.0.0-beta0709, Kotlin 1.4.32 & 1.5.10 with similar results.
Empty Screen Two
Main classes:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AndroidViewBinding(ContentMainBinding::inflate)
}
}
}
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = ComposeView(inflater.context).apply {
setContent {
FirstScreen( onButtonClick = {
findNavController().navigate(R.id.nav_second_fragment)
})
}
}
}
#Composable
fun FirstScreen(onButtonClick: () -> Unit) {
Column {
Text("Screen One", color = Color.Blue, fontSize = 30.sp)
Button(
onClick = {
onButtonClick() },
content = {
Text(text = "go to Screen Two", color = Color.White)
})
TextField(
value = "",
onValueChange = {},
label = { Text(stringResource(R.string.label_enter_value)) },
)
}
}
class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = ComposeView(inflater.context).apply {
setContent {
Column {
Text("Screen Two", color = Color.Blue, fontSize = 30.sp)
}
}
}
}
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 30
defaultConfig {
applicationId "com.example.composewithnavigation"
minSdk 28
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
viewBinding true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.10'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.ui:ui-viewbinding:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.0-beta01'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
}
As far as I know, fragments are discouraged in Compose for navigation. You are supposed to be creating multiple screens, like the FirstScreen Composable here. Anyway, I think the reason for the setContent not being called is that it is called from within the other setContent from your first fragment. Inside the setContent, you make the navigation call, which redirects it to the second fragment, which calls a setContent yet again. Because the second fragment will already be displayed inside the setContent of the FirstScreen Composable, your are actually nesting it, which might not be supported by compose , (I mean, makes sense).
That is why we recommend ditching the fragments and using Composables instead, which do not require explicitly calling setContent

How do you initiate Realm in Fragment?

I needed to initialize Realm from fragment but I get the following warning:
Required: Context
Found: Context
Code:
class MyRealm: Fragment() {
private lateinit var realm : Realm
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_realm, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Realm.init(context)
val configuration = RealmConfiguration.Builder()
.name("MyRealm.db")
.deleteRealmIfMigrationNeeded()
.schemaVersion(0)
.build()
Realm.setDefaultConfiguration(configuration)
realm = Realm.getDefaultInstance()
}
}
What did I do wrong?
Opening and closing the realm within try/catch block is recommended
try {
Realm realm = Realm.getDefaultInstance();
//Use the realm instance
}catch(Exception e){
//handle exceptions
}finally {
realm.close();
}
Also check for minSdkVersion >= 19 and Java >= 7
You can go through the official documentation for more details https://realm.io/docs/java/latest/#closing-realms
Also use getActivity() in place of (context).
The activity is a context (since Activity extends Context).

Can't use kotlin-extension in Adapter

Android Studio 3.6
build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"
android {
dataBinding {
enabled = true
}
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.myproject"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
Now in Activity I can use this:
import kotlinx.android.synthetic.main.event_details_activity.*
class EventDetailsActivity : AppCompatActivity() {
private lateinit var dataBinding: EventDetailsActivityBinding
companion object {
val EVENT = EventDetailsActivity.javaClass.canonicalName + "_EVENT"
private val TAG = EventDetailsActivity::class.java.name
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding =
DataBindingUtil.setContentView(this, R.layout.event_details_activity)
// THIS WORK FINE
eventTitleTextView.text = "Test"
Nice.
Now I want to use Kotlin extension in Adapter.
I try this:
import kotlinx.android.synthetic.main.event_list_item.*
class EventAdapter(
context: Context?,
data: List<*>?
) : DataBindingRecyclerViewAdapter(context, data) {
private var listener: AdapterListener? = null
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int
) {
super.onBindViewHolder(holder, position)
descriptionTextView.text = "TEST"
}
Here event_list_item.xml
<TextView
android:id="#+id/descriptionTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#{model.description}"
android:textColor="#android:color/darker_gray"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/titleTextView"
app:layout_constraintStart_toStartOf="#+id/titleTextView"
app:layout_constraintTop_toBottomOf="#+id/titleTextView" />
java file:
public abstract class DataBindingRecyclerViewAdapter extends RecyclerView.Adapter {
But I get error:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val Activity.descriptionTextView: TextView! defined in kotlinx.android.synthetic.main.event_list_item
public val Dialog.descriptionTextView: TextView! defined in kotlinx.android.synthetic.main.event_list_item
public val android.app.Fragment.descriptionTextView: TextView! defined in kotlinx.android.synthetic.main.event_list_item
public val androidx.fragment.app.Fragment.descriptionTextView: TextView! defined in kotlinx.android.synthetic.main.event_list_item
I have the same problem and i solved it by add this into gradle build file
androidExtensions {
experimental = true
}

Android Timber logging multiple times

Consider the following simple setup. 1 Fragment with 1 ViewModel:
Fragment
class TestFragment : Fragment() {
private val viewModel by lazy {
ViewModelProviders.of(this).get(TestViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_test, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.testLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
Log.d("###", "whattt")
})
}
}
ViewModel
class TestViewModel : ViewModel() {
private val myVariable = Log.d("###", "Test")
val testLiveData = MutableLiveData(false)
}
Why do I get a log output from both Fragment and ViewModel up to three times??
D/###: Test
D/###: whattt
D/###: Test
D/###: Test
D/###: whattt
D/###: whattt
After the comment from #tynn I realized that the problem might come from some actions happening before the fragment actually is involved. Long story short: I have a multi-module project: 1 app module, 1 data module and 1 network module.
Both the network module and the app module were planting a Timber DebugTree...So everything was logged twice facepalm
I searched a little bit if there is a good way to keep the modules independent in this regard. The only thing I could found was this SO answer:
https://stackoverflow.com/a/53872754/990129
object TimberLogImplementation {
fun initLogging() {
if(Timber.treeCount() != 0) return
if (BuildConfig.DEBUG) Timber.plant(DebugTree())
else Timber.plant(ReleaseTree())
}
}

Android Parcelable for Kotlin is not working

I am trying to pass data from one fragment to another but I am facing issue with sending data from parcelable from one fragment to another.
class MainFragment : Fragment() {
companion object {
public val KEY_PARSE_DATA = "parseData"
}
private var parseData: ParseData? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_main, container, false).apply {
val editName = findViewById(R.id.edit_Name) as EditText
val editSurname = findViewById(R.id.edit_Surname) as EditText
val buttonNext = findViewById(R.id.btn_Next) as Button
buttonNext.setOnClickListener(View.OnClickListener {
val fragment = AnotherFragment()
if (parseData != null) {
var parseData = ParseData(editName.text.toString(), editSurname.text.toString())
val fragment = AnotherFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, parseData)
fragment.setArguments(bundle)
fragmentManager.beginTransaction().add(R.id.fragment_container, fragment).commitAllowingStateLoss()
}
})
}
}
}// Required empty public constructor
Parcelable class for implementing it
#SuppressLint("ParcelCreator")
#Parcelize
data class ParseData(val firstName: String, val lastName: String) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()) {
}
override fun toString(): String {
return "ParseData(firstName='$firstName', lastName='$lastName')"
}
companion object : Parceler<ParseData> {
override fun ParseData.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
}
override fun create(parcel: Parcel): ParseData {
return ParseData(parcel)
}
}
}
And another fragment which grab data from parcelable class in android
class AnotherFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_another, container, false).apply {
val textName = findViewById<TextView>(R.id.textFirst)
val textSurname = findViewById<TextView>(R.id.textSecond)
val bundle = arguments
if (bundle != null) {
val parseData = bundle.getParcelable<ParseData>(KEY_PARSE_DATA)
textName.setText(parseData.firstName)
textSurname.setText(parseData.lastName)
}
}
}
}
I tried some example but I cant get clear idea how parcelable is implemented in andoid application based on kotlin and build.gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlinsecond"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
androidExtensions {
experimental = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
Add below code into your App Level gradle.
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
androidExtensions {
experimental = true
}
Now it is enough to add 'kotlin-parcelize' plugin.
So for example:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
//
androidExtensions {
experimental = true
}
}
The order of plugin matters. kotlin-android must be before kotlin-android-extension. Then clean project.
Unable to add kotlin android extension
I'd recommend taking a look at the #Parcelize annotation provided with the Kotlin Android Extensions, it generates all necessary boilerplate for you so you don't have to write and maintain that code yourself.
Need to add these two in your app level Gradle
apply plugin: 'kotlin-android-extensions'
androidExtensions {
experimental = true`
}
Kotlin plugin should be enabled before 'kotlin-android-extensions'
so change the order to
apply plugin: 'com.android.application' #1
apply plugin: 'kotlin-android' #2
apply plugin: 'kotlin-android-extensions' #3
Hello All I had solved this question and there was issue with fragment transaction and parcelable
MainActivity to setup fragment
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragment = DetailFragment()
manager.findFragmentById(R.id.fragmentContainer)
manager.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
}
}
First fragment to fill data
class DetailFragment : Fragment(), View.OnClickListener {
var editTextName: EditText? = null
var editTextLast: EditText? = null
var buttonNextFragment: Button? = null
var firstName: String? = null
var lastName: String? = null
companion object {
val KEY_PARSE_DATA = "detailData"
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_detail, container, false)
editTextName = view.findViewById(R.id.ed_firstName) as EditText
editTextLast = view.findViewById(R.id.ed_lastName) as EditText
buttonNextFragment = view.findViewById(R.id.btn_nextfragment) as Button
buttonNextFragment!!.setOnClickListener(this)
return view
}
override fun onClick(v: View?) {
firstName = editTextName!!.text.toString()
lastName = editTextLast!!.text.toString()
Toast.makeText(context, firstName, Toast.LENGTH_SHORT).show()
// val viewFragment = ViewFragment()
// val transaction = fragmentManager.beginTransaction()
// transaction.replace(R.id.fragmentContainer, viewFragment)
// transaction.commit()
var details = Details(firstName!!, lastName!!)
val viewFragment = ViewFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, details)
viewFragment.setArguments(bundle)
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, viewFragment)
transaction.commit()
}
}
Display data coming from parcelable
class ViewFragment : Fragment() {
var textViewName: TextView? = null
var textViewLastName: TextView? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_view, container, false)
textViewName = view.findViewById(R.id.text_name_another) as TextView
textViewLastName = view.findViewById(R.id.text_surname_another) as TextView
val bundle = arguments
if (bundle != null) {
val details = bundle.getParcelable<Details>(KEY_PARSE_DATA)
textViewName!!.setText(details.firstName)
textViewLastName!!.setText(details.lastName)
}
return view
}
}
Model class for implementation of parcelable
#Parcelize
class Details(val firstName: String, val lastName: String) : Parcelable
and my gradle file
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlintwo"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
androidExtensions {
experimental = true
}
}

Categories

Resources