Unresolved reference in findViewById when using Glide in Fragment in Kotlin - kotlin

I am new to Kotlin. I have an "unresolved reference:findViewByid" when I am using Fragment. I try to add "view?.findViewByid", but the app crashed and val imgUniverse returns "null". Is there a way to solve it?. Thanks.
I am trying to implement swipe pictures on different xml on multiple screens using PageViewer2.
KC
class Page1Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val imgUniverse = findViewById<ImageView>(R.id.image_universe) // Unresolved reference in findViewById
val imgFooter = findViewById<ImageView>(R.id.image_footer) // Unresolved reference in findViewById
Log.d("testing2: " , imgUniverse.toString())
Glide.with(this)
.load(R.drawable.universe3)
.fitCenter()
.centerCrop()
.into(imgUniverse!!)
Glide.with(this)
.load(R.drawable.footer)
.fitCenter()
.centerCrop()
.into(imgFooter!!)
return inflater.inflate(R.layout.fragment_page1, container, false)
}
}

You are searching for views before you are inflating (creating) them. The view is created in the last line with by inflating it using inflater.inflate(). Before that, none of the views even exists and therefore cannot be found and used.
Move all the code (except for the last line) into onViewCreated() and it will work.

Related

Kotlin, embed root_preferences in existing fragment

Kotlin, I want to embed root_preferences.xml in a fragment with existing content. I navigate using <navigation..> I want to embed the preferences like this:
// ... existing items
<include
android:id="#+id/summaryPrefs"
layout="#layout/fragment_container_settings"
/>
// ... more items here
What I did
Create new Settings Fragment. This does NOT create a layout file, but an XML file at res/xml/root_preferences.xml, make no changes to code.
Create a 'container fragment' new Fragment (Blank) name it, ContainerSettingsFragment. it creates the layout file fragment_container_settings.
Delete code in class ContainerSettingsFragment and replace with this:
// Make a container fragment that holds the preference xml.
class ContainerSettingsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_container_settings, container, false)
requireActivity().supportFragmentManager
.beginTransaction()
.replace(
R.id.settings, // this is the id in fragment_container_settings <FrameLayout>
SettingsFragment() // SR: ?? This loads the XML into this fragment
)
.commit()
// Inflate the layout for this fragment
return view
}
}
Replaced code in fragment_container_settings with this:
FrameLayout
android:id="#+id/settings"
android:layout_width="250dp"
android:layout_height="300dp"
Expected result: FrameLayout #+id/settings displays the contents of root_preferences
Actual: Size is 250dp x250dp but none of the items are displayed
Was easy, if using nav_graph, don't mix with .supportFragmentManager
I removed all the code in the container settings fragment.
): View? {
return inflater.inflate(R.layout.fragment_container_settings, container, false)
}
Completed layout in fragment_container_settings.xml
<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:id="#+id/settingsSummary"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.settingssummary.ContainerSettingsFragment">
</androidx.constraintlayout.widget.ConstraintLayout>
Used include to add to fragment_settings_summary

Viewmodel seems to be scoped to a fragment instead of activity. I am using navigation component as well

I am using navigation component and I have one activity in the app with so many fragments. I am trying to use one view model scoped to the activity in all the fragments but it working only in the 3 fragments nested in my home fragment. The home fragment which is the navigation host fragment is using a view pager to add 3 tabs which are 3 fragments within the home fragment. For instance I have like 3 tabs A, B, C. Between these three I can share data in view model successfully. 'A' has a detail fragment lets call it D. when I try to access shared viewModel in D it has null values which I know I have set already in A. I have a normal view model class.
in each fragment I instantiate view model like this
private val viewModel: MainActivityViewModel by activityViewModels()
it seems the viewModel is only scoped to the home fragment because it not working inside any detail fragment that I navigate to. Thank you. !
just in case there is something unusual about my view model class here is how it looks like
class MainActivityViewModel: ViewModel() {
var itemListLiveData = MutableLiveData<List<Item>>()
fun setItems(itemList: List<Item>) {
itemListLiveData.value = itemList
}
fun getItems() = itemListLiveData.value
}
You have to initialise your viewModel in onCreate method of your fragment.
// declare your viewModel like this
lateinit var viewModel : MainActivityViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// other code
// initialise your view model here
viewModel = ViewModelProvider(requireActivity()).get(MainActivityViewModel::class.java)
// other code
}
Also there is one mistake in your MainActivityViewModel class:
fun setItems(itemList: List<Item>) {
// set your live data like this
itemListLiveData.postValue(itemList)
}
Try instantiating your viewmodel like this
lateinit var viewModel: MainActivityViewModel
and in onViewCreated
viewModel = (activity as MainActivity).viewModel
This worked for me in the past when sharing a ViewModel.
Oops ! sorry I found the problem which has nothing to do with the activity view model not working. but I just leave my answer in case by chance some one else has the same issue.
The problem was here
fun setItems(itemList: List<Item>) {
itemListLiveData.value = itemList
}
in the fragment where I set this itemList, I clear this itemList in the fragments onStop() method so because itemListLiveData.value is referencing this itemList object it is also cleared. So I resolved it by instantiating it with a new object of itemList instead of referencing the itemList object. like below
fun setItems(itemList: List<Item>) {
itemListLiveData.value = ArrayList<Item>(itemList)
}

Viewmodel SavedStateHandle data lost after process death

I am trying to use savedStateHandle in my ViewModel and test it. My simple case is that I'm just storing an Int value in the SavedStateHandle and trying to get it again after the process death. But its not working at all. I am using the following dependency
implementation 'androidx.activity:activity-ktx:1.2.0-alpha08'
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha08"
Below is my Fragment which is having one button and one TextView. When I click the button, number 5 is stored in the savedStateHandle of the ViewModel and then the same was observed via the getLiveData method of the savedStateHandle and the number is displayed in the TextView. So, after process death, it should correctly restore the value of 5 and display it in the text view. Following is my fragment code
class FirstFragment : Fragment() {
private val viewModel by lazy{
ViewModelProvider(this).get(FirstFragmentVM::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_first.setOnClickListener {
viewModel.rememberNumber(5)
}
observeViewModel()
}
private fun observeViewModel(){
viewModel.rememberedNumber.observe(viewLifecycleOwner){
textview_first.text = it.toString()
}
}
}
and the following is my ViewModel code
#ExperimentalCoroutinesApi
#FlowPreview
class FirstFragmentVM(savedStateHandle: SavedStateHandle) : ViewModel() {
companion object{
private const val KEY_NUMBER_TO_REMEMBER="number:to:remember"
}
private val savedState=savedStateHandle
val rememberedNumber=savedState.getLiveData<Int>(KEY_NUMBER_TO_REMEMBER)
fun rememberNumber(number:Int){
savedState.set(KEY_NUMBER_TO_REMEMBER,number)
}
}
When run this app and click the button, the number 5 is stored in the savedStateHandle and its displaying "5" correcly in the text view. But when I put the app in the background and kill the process using adb and then restart the process from the recent screen, the entire app is restarted and its not showing the remembered number in the textView. Instead, its showing "Hello" which I set in the layout xml. I am killing the process as follows
adb shell
am kill <package name>
Anyone kindly help me why its not at all working for me? What am I doing wrong here?
Finally, I found the reason. The app restarts if we kill the app process after launching it directly from the IDE. Instead, If I close the app by swiping it from recents, launch it from the app drawer and then killing it via adb works and restores the state of the app perfectly.

navController.currentDestination + findFragmentById returns null

I'm trying to find the id for the last fragment navigated to using navController since I'm using the Navigation component instead of fragment managers:
override fun onBackPressed() {
val fragment = this.supportFragmentManager.findFragmentById(navController.currentDestination!!.id)
(fragment as IOnBackPressed).onBackPressed().not().let {
if (it) super.onBackPressed()
}
}
However, I keep getting the following error message when I trigger this code:
kotlin.TypeCastException: null cannot be cast to non-null type com.google.apps.sanctified.IOnBackPressed
at com.google.apps.sanctified.SanctifiedActivity.onBackPressed(SanctifiedActivity.kt:41)
I walked through the code with the debugger, and it appears that navController is finding the correct destination in its backstack and returning the correct id. However, findFragmentById() doesn't seem to have any record of the fragment, and always returns null. The documentation says that this function:
Finds a fragment that was identified by the given id either when inflated from XML or as the container ID when added in a transaction.
Obviously I'm not using transactions, but it should be updated at inflation. Perhaps I'm doing something wrong there:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DetailFragmentBinding.inflate(inflater, container, false)
return binding.root
}
Needed to use this:
navHost.childFragmentManager.findFragmentById(R.id.nav_host)

CardStackView null

class HomeFragment : Fragment(), CardStackListener {
val cardStackView = card_stack_view
val manager = CardStackLayoutManager(getActivity(), this)
private val adapter by lazy { CardStackAdapter(createSpots()) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val homeFragmentView : View = inflater!!.inflate(R.layout.fragment_home, container, false)
cardStackView.layoutManager = manager
setupCardStackView()
setupButton()
return homeFragmentView
}
*Error message is "cardStackView must not be null"
The error is in the "cardStackView.layoutManager = manager"
What can i do for resolving this problem... *
add a question mark after cardStackView like this:
cardStackView?.layoutManager = manager
I guess cardStackView is nullable and IDE says this line may results NullPointerException
Can you add which CardStackView you are using? There are several libraries that implement such a view (some of them have good documentation, like https://github.com/yuyakaido/CardStackView and https://github.com/loopeer/CardStackView).
You could look for examples of a CardStackView for Android being used on Stack Overflow or by using Google.
Update
The issue could be caused by the manager field possibly being null. What happens if you do the initialization completely within the function?
val manager = CardStackLayoutManager()
val adapter = CardStackAdapter()
val cardStackView = findViewById<CardStackView>(R.id.card_stack_view)
cardStackView.layoutManager = manager
cardStackView.adapter = adapter