I'm trying to list song files on the device using RecycleView and get the following error:
Error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.recyclerview.widget.RecyclerView.setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager)' on a null object reference
fragments.LoadFragment.loadTrackData(LoadFragment.kt:50)
fragments.LoadFragment.onCreate(LoadFragment.kt:36)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2949)
at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:278)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Line 50 is recycler_view.layoutManager = layoutManager
and line 36 is loadTrackData() calling the function that line 50 is in.
These lines are in the fragment I'm working in:
import android.Manifest
import android.content.pm.PackageManager
import android.database.Cursor
import android.os.Bundle
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.repea.Adapters.TrackListAdapter
import com.example.repea.R
import com.example.repea.TrackData.TrackData
import kotlinx.android.synthetic.main.fragment_load.*
class LoadFragment : Fragment() {
var trackData:ArrayList<TrackData> = ArrayList()
var trackListAdapter:TrackListAdapter?=null
companion object{
val PERMISSION_REQUEST_CODE = 12
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (context?.let { ContextCompat.checkSelfPermission(it.applicationContext,Manifest.permission.READ_EXTERNAL_STORAGE) } !=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this.requireActivity(),
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE)
}else{
loadTrackData()
}
}
fun loadTrackData(){
var trackCursor:Cursor? = activity?.applicationContext?.contentResolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,null,null,null)
while (trackCursor!=null && trackCursor.moveToNext()){
var trackTitle = trackCursor.getString(trackCursor.getColumnIndex(MediaStore.Audio.Media.TITLE))
var trackLength = trackCursor.getString(trackCursor.getColumnIndex(MediaStore.Audio.Media.DURATION))
trackData.add(TrackData(trackTitle,trackLength))
}
trackListAdapter = TrackListAdapter(trackData)
var layoutManager = LinearLayoutManager(activity?.applicationContext)
recycler_view.layoutManager = layoutManager
recycler_view.adapter = trackListAdapter
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_REQUEST_CODE){
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
Toast.makeText(context?.applicationContext,"Permission Granted",Toast.LENGTH_SHORT).show()
loadTrackData()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_load, container, false)
}
}
Here's the adapter:
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.recyclerview.widget.RecyclerView
import com.example.repea.R
import com.example.repea.TrackData.TrackData
class TrackListAdapter(TrackData:ArrayList<TrackData>):RecyclerView.Adapter<TrackListAdapter.TrackListViewHolder>() {
val dtrackData = TrackData
class TrackListViewHolder(itemView: ActionMenuItemView):RecyclerView.ViewHolder(itemView){
var titleTV:TextView
var lengthTV:TextView
var artTV:ImageView
init {
titleTV = itemView.findViewById(R.id.track_title_view)
lengthTV = itemView.findViewById(R.id.track_length_view)
artTV = itemView.findViewById(R.id.track_art_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackListViewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.load_item,parent,false)
return TrackListViewHolder(view as ActionMenuItemView)
}
override fun onBindViewHolder(holder: TrackListViewHolder, position: Int) {
var data = dtrackData[position]
var trackName = data.dTrackTitle
var trackLength = data.dTrackLength
holder.titleTV.text = trackName
holder.lengthTV.text = trackLength
}
override fun getItemCount(): Int {
return dtrackData.size
}
}
And the widget:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.LoadFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
android:clipToPadding="false"/>
</RelativeLayout>
What's gone wrong here?
You are not bind recycler_view with any view so it gives null, you should reference any view after inflate the layout that hold views.
here an example of using RecyclerView into Fragment class
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_blank, container, false)
view.recyclerView.layoutManager = LinearLayoutManager(activity)
view.recyclerView.adapter = MainAdapter()
return view
}
The error message saying that your recycler view is null and that because you used it before it created, so you should move the initialization to the onCreateView method
my thouts:
if (context?.let { ContextCompat.checkSelfPermission(it.applicationContext,Manifest.permission.READ_EXTERNAL_STORAGE) } !=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this.requireActivity(),
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE)
}else{
loadTrackData()
}
Try to muve this in onViewCreated()
Because your views inst loaded yet, when onCreate() is called
And i dint found where are you initialize recycler_view?
Related
I am trying to develope an app which lets the user choose and read a csv file from his phone, so the app can set a variable with the chosen file content (string). This variable can then be used to set the text of a TextView.
I already was able to let the user of my app choose the desired file. I couldn't get the file content though. And I've already seen that onActivityResult and startActivityForResult are depreacated which means that a lot of tutorials are outdated. Plus I was not able to get a solution to my problem by the documentation here.
This picture shows you, that after the user chooses his csv file, the textView is not changing.
This is what I have come up with so far for my ContentFragment.kt file. As you can see I already have a line for setting the TextView text to the content of a csv file which is located in the asset folder. That works great (here it is commented out - only there fyi), but I would like to do the same for the user's csv file:
package com.example.myapplication
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.myapplication.databinding.FragmentContentBinding
import kotlinx.android.synthetic.main.fragment_content.*
import java.io.BufferedReader
import java.io.InputStreamReader
class ContentFragment : Fragment() {
var _binding: FragmentContentBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentContentBinding.inflate(inflater, container, false)
val file = BufferedReader(InputStreamReader(resources.assets.open("Testfile.csv"))).use {
it.readText()
} // The variable is unused. This line of code only exists to demonstrate that the file_content.settext(file) in the TestButton click event is working.
binding.TestButton.setOnClickListener {
// file_content.setText(file) // <-- This line works
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
startActivity(Intent.createChooser(intent, "Select a file"))
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?) {
resultData?.data?.also { uri ->
val file = BufferedReader(InputStreamReader(resources.assets.open(resultData.toString()))).use {
it.readText()
}
file_content.setText(file)
}
}
}
This is the xml of the fragment:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ContentFragment"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/TestButton"
android:text="Import File"
tools:ignore="MissingConstraints"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="In here the content of the file will appear."
android:id="#+id/file_content"
tools:ignore="MissingConstraints" />
</LinearLayout>
And here are more files, which are not so necessary:
MainActivity.kt:
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="56dp"
app:layout_constraintBottom_toBottomOf="parent">
<fragment
android:id="#+id/fragment"
class="com.example.myapplication.ContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Replace:
val file = BufferedReader(InputStreamReader(resources.assets.open(resultData.toString()))).use {
it.readText()
}
with:
val file = BufferedReader(InputStreamReader(requireContext().contentResolver.openInputStream(uri))).use {
it.readText()
}
Note that your use of */* for the MIME type means that the user can choose non-text content. Consider using text/*.
I was able to solve my problem by editing ContentFragment.kt. Now it works to let the user choose a csv-file and show its content in a textView.
package com.example.myapplication
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import com.example.myapplication.databinding.FragmentContentBinding
import kotlinx.android.synthetic.main.fragment_content.*
import java.io.BufferedReader
import java.io.InputStreamReader
class ContentFragment : Fragment() {
var _binding: FragmentContentBinding? = null
val binding get() = _binding!!
private val request = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null) {
val inputStream = requireContext().contentResolver.openInputStream(uri)
val file = BufferedReader(InputStreamReader(inputStream)).use {
it.readText()
}
file_content.setText(file)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentContentBinding.inflate(inflater, container, false)
binding.TestButton.setOnClickListener {
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
request.launch("*/*")
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
list_users.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"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/user_list_bg"
android:layout_margin="10dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/user_profile_image_view"
android:layout_width="90sp"
android:layout_height="90sp"
android:src="#drawable/user_image_placeholder"
android:layout_margin="12dp"
app:border_width="1sp"
android:clickable="true"
android:focusable="true">
</de.hdodenhof.circleimageview.CircleImageView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="#+id/user_name_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hardik Dhuri"
android:layout_marginBottom="10dp"
android:textSize="24sp"/>
<TextView
android:id="#+id/last_message_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Bro"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
fragment_chats.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.fragments.ChatsFragment"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list_users_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/list_users"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</FrameLayout>
ListUsersAdapter.kt
package com.example.samvach.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.samvach.R
import com.example.samvach.models.User
class ListUsersAdapter(private val userList: ArrayList<User>) : RecyclerView.Adapter<ListUsersAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_users, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = userList[position]
holder.name.text = item.name
}
override fun getItemCount(): Int {
return userList.size
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val name: TextView = itemView.findViewById(R.id.user_name_tv)
}
}
ChatsFragment.kt
Here I am getting data from firebase. I checked the data by logging it and it seems fine only thing i can't figure out is where is the actual error in my recycler view. I create few users manually to test and they don't show up either.
package com.example.samvach.main.fragments
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.example.samvach.R
import com.example.samvach.adapters.ListUsersAdapter
import com.example.samvach.databinding.FragmentChatsBinding
import com.example.samvach.models.User
import com.google.firebase.database.*
class ChatsFragment : Fragment() {
private lateinit var binding: FragmentChatsBinding
private lateinit var dbref: DatabaseReference
private lateinit var usersArrayList: ArrayList<User>
companion object {
fun newInstance() = ChatsFragment()
}
private lateinit var viewModel: ChatsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_chats, container, false)
}
#Deprecated("Deprecated in Java")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this)[ChatsViewModel::class.java]
binding = FragmentChatsBinding.inflate(layoutInflater)
binding.listUsersRv.setHasFixedSize(true)
usersArrayList = arrayListOf(
User(
name = "Banana"
),
User(
name = "Apple"
),
User(
name = "Kiwi"
),
User(
name = "Orange"
)
)
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
// getUserData()
}
private fun getUserData() {
dbref = FirebaseDatabase.getInstance().getReference("users")
dbref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
usersArrayList.clear()
if (snapshot.exists()) {
for (userSnapshot in snapshot.children) {
val user = userSnapshot.getValue(User::class.java)
Log.i("USER", "${user!!.name}")
usersArrayList.add(user)
}
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
User.kt
package com.example.samvach.models
data class User (
var email: String?= null,
var name: String?= null,
var profilePicture: String?= null,
var uid: String?= null,
)
You have an error in binding = FragmentChatsBinding.inflate(layoutInflater), try to init your binding in onCreateView like this:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_chats, container, false)
binding = FragmentChatsBinding.bind(view)
return binding.root
}
or move your code of onActivityCreated to onViewCreated and use view instance
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_chats, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentChatsBinding.bind(view)
viewModel = ViewModelProvider(this)[ChatsViewModel::class.java]
binding.listUsersRv.setHasFixedSize(true)
usersArrayList = arrayListOf(
User(
name = "Banana"
),
User(
name = "Apple"
),
User(
name = "Kiwi"
),
User(
name = "Orange"
)
)
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
// getUserData()
}
I'm new to android development in Kotlin. I would like to know how to implement a dynamic two column listView.
Here is what I am trying to achieve:
I should be able to dynamically add new elements by pressing the ADD button. The ADD button will open a dialog window where the user will fill out the info (description and price) of the new item.
The listView should look like this:
item price
item price
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import com.example.recantoanimal.R
import kotlinx.android.synthetic.main.custom_dialog_fragment.*
class MainActivity : AppCompatActivity() {
private var list = mutableListOf<Model>()
private var adapter: ListViewAdapter? = null
private var addButton: Button? = null
var dialogDescription: TextView? = null
var dialogPrice: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var listView = findViewById<ListView>(R.id.listView)
addButton = findViewById<View>(R.id.addItem) as Button?
dialogDescription = findViewById(R.id.dialog_description)
dialogPrice = findViewById(R.id.dialog_price)
list.add(Model("rice", "10"))
list.add(Model("banana", "3"))
list.add(Model("apple", "2"))
listView.adapter = ListViewAdapter(this, R.layout.list_row, list)
addButton!!.setOnClickListener {
MyCustomDialog().show(supportFragmentManager, "MyCustomFragment")
list.add(Model(dialog_description.text.toString(), dialog_price.text.toString()))
listView.adapter = ListViewAdapter(this, R.layout.list_row, list)
adapter!!.notifyDataSetChanged()
}
}
}
class Model {
var description: String = ""
var price: String = ""
constructor(){}
constructor(description: String, price: String) {
this.description = description
this.price = price
}
}
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.example.recantoanimal.R
class ListViewAdapter(var mCtx: Context, var resources: Int, var items: List<Model>): ArrayAdapter<Model>(mCtx, resources, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater: LayoutInflater = LayoutInflater.from(mCtx)
val view: View = layoutInflater.inflate(R.layout.list_row, null)
val descriptionTextView: TextView = view.findViewById(R.id.description)
val priceTextView: TextView = view.findViewById(R.id.price)
val mItem: Model = items[position]
descriptionTextView.text = mItem.description
priceTextView.text = mItem.price
return view
}
}
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.example.recantoanimal.R
class MyCustomDialog: DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
getDialog()!!.getWindow()?.setBackgroundDrawableResource(R.drawable.round_corner);
return inflater.inflate(R.layout.custom_dialog_fragment, container, false)
}
override fun onStart() {
super.onStart()
val width = (resources.displayMetrics.widthPixels * 0.85).toInt()
val height = (resources.displayMetrics.heightPixels * 0.40).toInt()
dialog!!.window?.setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".activities.MainActivity"
>
<Button
android:id="#+id/addItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="Add" />
<ListView
android:id = "#+id/listView"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:divider = "#null"
android:layout_below="#+id/addItem" />
</RelativeLayout>
This question already has answers here:
ViewPager and RecyclerView issue with fragment transition
(2 answers)
Closed 1 year ago.
ViewPager2 Caused by IllegalStateException FragmentManager is already executing transactions
Replicated Crash : while rotating screen
How to fix Viewpager2 FragmentManager is already executing transactions?
Full source code:
https://github.com/stevdza-san/ViewPager2-with-Navigation-Component-TestApp
ViewPagerFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.test.zigmaster.databinding.FragmentViewPagerBinding
class ViewPagerFragment : Fragment() {
var binding : FragmentViewPagerBinding?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
// val view = inflater.inflate(R.layout.fragment_view_pager, container, false)
binding = FragmentViewPagerBinding.inflate(inflater)
val fragmentList = arrayListOf<Fragment>(
FirstScreen(),
SecondScreen(),
ThirdScreen()
)
val adapter = ViewPagerAdapter(
fragmentList,
requireActivity().supportFragmentManager,
lifecycle
)
binding!!.viewPager.adapter = adapter
return binding!!.root
}
override fun onDestroyView() {
super.onDestroyView()
binding=null
}
}
ViewPagerAdapter.kt
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
class ViewPagerAdapter(
list: ArrayList<Fragment>,
fm: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fm, lifecycle) {
private val fragmentList = list
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
}
fragment_view_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.app_intro.ViewPagerFragment">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FirstScreen.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.test.zigmaster.R
import com.test.zigmaster.databinding.FragmentFirstScreenBinding
class FirstScreen : Fragment() {
private var binding : FragmentFirstScreenBinding?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
binding = FragmentFirstScreenBinding.inflate(inflater)
val viewPager = activity?.findViewById<ViewPager2>(R.id.viewPager)
binding!!.next.setOnClickListener {
viewPager?.currentItem = 1
}
return binding!!.root
}
override fun onDestroyView() {
super.onDestroyView()
binding=null
}
Change view.viewPager.adapter = adapter to
Handler(Looper.getMainLooper()).post {
view.viewPager.adapter = adapter
}
in https://github.com/stevdza-san/ViewPager2-with-Navigation-Component-TestApp/blob/master/app/src/main/java/com/jovanovic/stefan/mytestapp/onboarding/ViewPagerFragment.kt
I am trying to create a tabular layout for my Android application through an adapter class. However, the layout represented in the ViewPager does not change when I click on different tabs. Below is my code:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:background="#ffffff"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay"/>
<ScrollView 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
tools:context=".MainActivity"
app:tabTextAppearance="#style/MyCustomTextAppearance"
>
<!-- The tabular layout -->
<com.google.android.material.tabs.TabLayout
android:id="#+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textAllCaps="false"
android:background="#ffffff"
>
</com.google.android.material.tabs.TabLayout>
<!-- Contains the fragment corresponding to each tab -->
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
total_fragment.xml, individual_fragment.xml, usage_fragment.xml are all very basic layouts with just a TextView in them.
Below is my Kotlin code (MainActivity.kt):
import PageAdapter
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import android.util.Log
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
var tabLayout: TabLayout? = null
var viewPagerReference: ViewPager? = null
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tabLayout = findViewById<TabLayout>(R.id.tablayout)
viewPagerReference = findViewById<ViewPager>(R.id.viewPager)
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabA"))
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabB"))
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabC"))
tabLayout!!.tabGravity = TabLayout.GRAVITY_FILL
val adapter = PageAdapter(this, supportFragmentManager, tabLayout!!.tabCount)
viewPager!!.adapter = adapter
viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
tabLayout!!.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
Log.i("TextStats","NEW TAB SELECTED: " + tab.position)
viewPager!!.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
}
For the adapter class (PageAdapter.kt):
import android.content.Context
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import com.example.textstats.IndividualFragment
import com.example.textstats.TotalFragment
import com.example.textstats.UsageFragment
class PageAdapter(private val myContext: Context, fm: FragmentManager, internal var totalTabs: Int) : FragmentPagerAdapter(fm) {
// this is for fragment tabs
override fun getItem(position: Int): Fragment? {
Log.i("TextStats", "POSITION = " + position);
when (position) {
0 -> {
// val homeFragment: HomeFragment = HomeFragment()
return TotalFragment()
}
1 -> {
return IndividualFragment()
}
2 -> {
// val movieFragment = MovieFragment()
return UsageFragment()
}
else -> return null
}
}
// this counts total number of tabs
override fun getCount(): Int {
return totalTabs
}
}
TotalFragment, UsageFragment, and IndividualFragment have all the same code below, except for the layout id being different (R.layout.[layout name]):
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class TotalFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.total_fragment, container, false)
}
}
add this line after set adapter
tabLayout.setupWithViewPager(viewPager)
class LayoutPagerAdapter internal
constructor(var mContext: Context):PagerAdapter() {
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object` as ImageView
}
private val sliderImageId = intArrayOf(
R.drawable.sym_action_call,R.drawable.presence_audio_away
)
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val imageView = ImageView(mContext)
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP)
imageView.setImageResource(sliderImageId[position])
(container as ViewPager).addView(imageView, 0)
return imageView
}
override fun destroyItem(
container: ViewGroup,
position: Int,
`object`: Any
) {
(container as ViewPager).removeView(`object` as ImageView)
}
override fun getCount(): Int {
return sliderImageId.size
}
}