ViewPager2 must not be null in NavigationDrawer and SwipeViewTabs - kotlin

I am trying to link my Navigation Drawer with SwipeView Tabs, the problem is that the logcat tells me that my Viewpager must not be null, I have tried to solve this problem in many ways but could not.
PageAdaper.kt
class ViewPagerAdapter(fragmanetActivity: TabFragment): FragmentStateAdapter(fragmanetActivity) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
when (position) {
0 -> return FirstFragment()
1 -> return SecondFragment()
2 -> return ThirdFragment()
}
return Fragment()
}
}
Fragment
class TabFragment : Fragment() {
private val adapter by lazy { ViewPagerAdapter(this) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val x = inflater.inflate(R.layout.contain_main, container, false)
pager.adapter = adapter // This is the error
TabLayoutMediator(tab_layout, pager) { tab, position ->
when (position) {
0 -> tab.text = "option1"
1 -> tab.text = "option2"
2 -> tab.text = "option3"
}
}.attach()
return x
}
}
contain_main.xml
I linked this file with ( class TabFragment : Fragment() )
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabSelectedTextColor="#E91E63" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

In your class TabFragment you are trying to access pager view. Since you are in the onCreateView method, synthetic doesn’t know how to access view and give you reference on pager.
You can do that in onViewCreated and later callbacks, or you can use val x to access pager.MikibeMiki
code:
class TabFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.contain_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter by lazy { ViewPagerAdapter(this) }
pager.adapter = adapter
TabLayoutMediator(tab_layout, pager) { tab, position ->
when (position) {
0 -> tab.text = "option1"
1 -> tab.text = "option2"
2 -> tab.text = "option3"
}
}.attach()
}
}

Related

Artefacts when implementing expandable items in RecyclerView

I'm trying to make a simple RecyclerView with expandable items using Kotlin. Basically a list of bins which expand out to show descriptions of what should go in them. I tried to follow the Android Studio Recycler View example as closely as possible. The problem is when I expand each item, some artefacts would show.
Unexpanded view
Expanded view
Note: The expanded view is the result of pressing on "Recycling Bin", the artefact is made up of the bin's description text and also "Garden Waste Bin" somehow being duplicated.
Below is my implementation
Adapter class
class BinAdapter(private val dataSet: List<Bin>) : RecyclerView.Adapter<BinViewHolder>() {
var expandedPosition = -1
var previousExpandedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bin_row_item, parent, false)
return BinViewHolder(view)
}
override fun onBindViewHolder(viewHolder: BinViewHolder, position: Int) {
viewHolder.itemView.isActivated
viewHolder.titleView.text = dataSet[position].name
viewHolder.descriptionView.text = dataSet[position].description
val isExpanded = position == expandedPosition
viewHolder.descriptionView.visibility = if (isExpanded) View.VISIBLE else View.GONE
viewHolder.itemView.isActivated = isExpanded
if (isExpanded) {
previousExpandedPosition = position
}
viewHolder.itemView.setOnClickListener {
expandedPosition = if (isExpanded) -1 else position
notifyItemChanged(previousExpandedPosition)
notifyItemChanged(position)
}
}
override fun getItemCount() = dataSet.size
Fragment class
class BinLookupFragment : Fragment() {
private var _binding: FragmentBinLookupBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBinLookupBinding.inflate(inflater, container, false)
binding.binRecyclerView.adapter = BinAdapter(BinList(resources))
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ViewHolder class
class BinViewHolder(itemView: View) : ViewHolder(itemView) {
val titleView: TextView
val descriptionView: TextView
init {
titleView = itemView.findViewById(R.id.titleView)
descriptionView = itemView.findViewById(R.id.descriptionView)
}
}
Row item layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/titleView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_title_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/descriptionView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_description_height"
app:layout_constraintTop_toBottomOf="#id/titleView"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
fragment layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/bin_recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="200dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Has anyone come across this before or have some debugging tips?

RecyclerView must not be null - within fragment

I'm trying to view a recyclerview within a fragment that immediately runs via MainActivity. I keep getting the 'itemList_hot must not be null' right on startup. I tried initializing it in the MainActivity but I must be doing something wrong. Very new to Android Studio but this is a roadblock I can't seem to figure out. Code below, thanks.
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager)
val viewPager: ViewPager = findViewById(R.id.view_pager)
viewPager.adapter = sectionsPagerAdapter
val tabs: TabLayout = findViewById(R.id.tabs)
tabs.setupWithViewPager(viewPager)
//error is here
val recyclerView : RecyclerView = findViewById(R.id.listItems_hot)
recyclerView.layoutManager = LinearLayoutManager(this)
val dataManager = DataManager()
recyclerView.adapter = HotRecyclerAdapter(this, dataManager.getData())
}
override fun onResume() {
super.onResume()
}
}
Fragment XML
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/listItems_hot"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/item_list_hot">
</androidx.recyclerview.widget.RecyclerView>
Fragment code
class Tab1_Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_tab1, container, false)
}
}
Error code
findViewById(R.id.listItems_hot) must not be null

How can I attach an adapter to my fragment?

I use Kotlin in SDK 29.
I would like to make a recyclerView in my fragment. When I run the device, it doesn't crash, the fragment appear but not the Recycler View and I have the following error :
E/RecyclerView: No adapter attached; skipping layout
Here is my code :
Adapter
Import :
import android.content.Context
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.givenaskv1.R
import kotlinx.android.synthetic.main.item_post.view.*
Code :
class PostsAdapter (val context: Context, val posts : List<Post>) :
RecyclerView.Adapter<PostsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.item_post, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = posts.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(posts[position])
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(post: Post) {
itemView.tvUsername.text = post.user?.firstName
itemView.tvDescription.text = post.description
Glide.with(context).load(post.imageUrl).into(itemView.ivPost)
itemView.tvRelativeTime.text = DateUtils.getRelativeTimeSpanString(post.creationTimeMs)
}
}
}
Fragment
Import :
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.Navigation
import com.example.givenaskv1.R
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.android.synthetic.main.fragment_page_profil.*
Code :
class FragmentPageProfil : BottomSheetDialogFragment(), OnMapReadyCallback {
private lateinit var googleMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
readFireStoreData()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_page_profil, container, false)
readFireStoreData()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnProfilOption.setOnClickListener {
val action = FragmentPageProfilDirections.actionFragmentPageProfil2ToProfilOption()
Navigation.findNavController(it).navigate(action)
}
retourBtnprofil.setOnClickListener {
val action = FragmentPageProfilDirections.actionFragmentPageProfil2ToNotificationFragment()
Navigation.findNavController(it).navigate(action)
}
btnCalendar.setOnClickListener {
val action = FragmentPageProfilDirections.actionFragmentPageProfil2ToFragmentCalendrier()
Navigation.findNavController(it).navigate(action)
}
mapProfil.onCreate(savedInstanceState)
mapProfil.onResume()
mapProfil.getMapAsync(this)
}
override fun onMapReady(map: GoogleMap?) {
map?.let {
googleMap = it
}
}
fun readFireStoreData() {
val db = FirebaseFirestore.getInstance()
db.collection("users")
.get()
.addOnCompleteListener {
val result: StringBuffer = StringBuffer()
if(it.isSuccessful) {
for(document in it.result!!) {
result.append(document.data.getValue("firstName")).append(" ")
}
nomUtilisateur.setText(result)
}
}
}
}
Fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".Flux.Flux">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvPosts"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Can you please help me ?
Thank you !
You forgot to add layoutManager and orientation that's it
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical" />
And You can also fix this function as after return the function doesn't call ever
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
readFireStoreData()
return inflater.inflate(R.layout.fragment_page_profil, container, false)
}
E/RecyclerView: No adapter attached; skipping layout error could be happened when adapter has no data.
Move readFireStoreData() function to before return inflater code
like as below.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
readFireStoreData()
return inflater.inflate(R.layout.fragment_page_profil, container, false)
}

How to make a button Work within a fragment (Kotlin)

I Have an activity fragment whereby i have it call out values from a separate data class and also a .xml file
I want the button in the .xml file to be accessible in the Kotlin CLASS.however, an error keeps showing that the declared function is unreachable.
What i have done:
the kotlin class page:
class HomeFragment : Fragment() {
private val personcolletionref =Firebase.firestore.collection( "users")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
submitbutton.setOnClickListener {
}
}
private fun savePerson (person: Person) = CoroutineScope(Dispatchers.IO).launch {
try {
personcolletionref.add(person).await()
withContext(Dispatchers.Main) {
Toast.makeText(activity, "Successful", Toast.LENGTH_LONG).show()
}
}catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(activity, e.message, Toast.LENGTH_LONG).show()
}
}
}
}
the button in the xml file to be called:
<Button
android:id="#+id/submitbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="268dp"
android:text="Submit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/edittextage"
app:layout_constraintStart_toStartOf="#+id/edittextage" />
In the onCreateView method, you just have to return the view. Override the following method and write your button on click listener init
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
submitbutton.setOnClickListener {
}
}

Kotlin FragmentStatePagerAdapter shows empty views

I am trying to set up ViewPager adapter inside a fragment to show another fragments. The problem is that when called ViewPager automatically calls the first two fragments while displaying only blank page.
This is how ViewPager is set:
class SwipePagerFragment : Fragment() {
private lateinit var pager: ViewPager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_swipe_pager, container, false)
pager = view.findViewById(R.id.fragmentSwipePager_pager)
val pagerAdapter = ScreenSlidePagerAdapter(childFragmentManager)
pager.adapter = pagerAdapter
return view
}
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
override fun getCount(): Int = 3
override fun getItem(position: Int): Fragment = when (position) {
0 -> MeFragment()
1 -> AccountFragment()
3 -> SearchFragment()
else -> ContactsFragment()
}
}
override fun onDestroyView() {
val frContainer = (activity as MainActivity).findViewById<View>(R.id.activityMain_fragment) as ViewGroup
frContainer.removeAllViews()
super.onDestroyView()
}
}
This is what I get in logcat:
... D/FRG ME: # onCreateView
... D/FRG Account: # onCreateView
I think you are trying to add 4 fragments in ViewPager and provided getCount() = 3
override fun getCount(): Int = 4
Also, make changes in Adapter as follows,
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm) {
override fun getCount(): Int = 4
override fun getItem(position: Int): Fragment = when (position) {
0 -> MeFragment()
1 -> AccountFragment()
2 -> SearchFragment()
else -> ContactsFragment()
}
}
Oh, LOL, I have figured it out.
I have set layout for Fragment I used for ViewPager as follows:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
android:id="#+id/fragmentSwipePager_rootScroll"
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"
android:padding="5dp"
tools:context=".SwipePagerFragment">
<android.support.v4.view.ViewPager
android:id="#+id/fragmentSwipePager_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
</ScrollView>
The problem was in "layout_height" property of ScrollView as it was set to "wrap_content" and with no actual content with non-zero height inside it resulted in drawing zero-height line and displaying Fragments contents inside it. Changing "layout_height" to "match_parent" solves my problem.