Context in fragment for recyclerview - Kotlin - kotlin

I should be able to resolve this as their are so many similar posts on Stack but my brain is just not getting it...
I'm fairly new to android (from flutter) and I want a recyclerview in a fragment... moreover the recycler view doesn't use XML but a custom view class created by some JSON/GSON. None of the online tutorials really cover this and the Stack articles deal with one or another part but not all combined.
I think I have setup my custom views and adapter ok, but no matter what I try I am getting errors from my fragment, mostly related to the context and null parameters.
Here is my fragment class:
class MySquibsFragment : Fragment() {
//var squibGrid: RecyclerView = RecyclerView(requireContext())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val v: View = inflater.inflate(R.layout.fragment_my_squibs, container, false)
//squibGrid = v.findViewById<RecyclerView>(R.id.squibgrid)
return v
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val squibList: ArrayList<SquibModel> = ArrayList()
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
//squibGrid.layoutManager = LinearLayoutManager(activity)
//val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
//squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}
The parts that are commented out are the lines that are throwing errors. I've tried moving all of onCreate into onActivityCreated and I've tried using lateinit to get the context, I just can't get my head around it.

Writing val squibGrid = view.findViewById<RecyclerView>(R.id.squibgrid) at the top means that you're trying to find the view when your fragment object is initialized. At that time, the fragment's view is not inflated, so you'll get an exception.
The fragment's view is inflated in onCreateView()
val v: View = inflater.inflate(R.layout.fragment_my_squibs, container, false)
return v
Try moving everything to onViewCreated(). According to the docs for fragment's lifecycle onViewCreated() is called after onCreateView(), i.e after the view is inflated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val squibGrid = view.findViewById<RecyclerView>(R.id.squibgrid)
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
squibGrid.layoutManager = LinearLayoutManager(activity)
val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}

Context in fragments will be available after onAttachContext method is called, also your views won't be created before onCreateView, it will crash if you try to access squibGrid in onCreate, You can safely bet on onViewCreated method where context and views are already available to consume
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val squibList: ArrayList<SquibModel> = ArrayList()
try {
val jsonString = getJSONFromAssets()!!
val squibs = Gson().fromJson(jsonString, Squibs::class.java)
//squibGrid.layoutManager = LinearLayoutManager(activity)
//val itemAdapter = MySquibsAdapter(requireActivity(), squibs.squibs)
//squibGrid.adapter = itemAdapter
} catch (e: JSONException) {
e.printStackTrace()
}
}

Related

y cannot be cast to com.example.runtrackerapp.databinding.ActivityMainBinding?

I am developing new app when I run project I am getting following java.lang.ClassCastException: com.example.runtrackerapp.ui.MainActivity cannot be cast to com.example.runtrackerapp.databinding.ActivityMainBinding
at com.example.runtrackerapp.ui.fragments.SetupFragment.writePersonalDataToSharedPref(SetupFragment.kt:84)
at com.example.runtrackerapp.ui.fragments.SetupFragment.onViewCreated$lambda-0(SetupFragment.kt:60)
at com.example.runtrackerapp.ui.fragments.SetupFragment.$r8$lambda$4d3caNvVygzMMPPCQbih5sKklFY(Unknown Source:0)
at com.example.runtrackerapp.ui.fragments.SetupFragment$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:6614)
at android.view.View.performClickInternal(View.java:6587)
at android.view.View.access$3100(View.java:787)
at android.view.View$PerformClick.run(View.java:26122)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
below my SetupFragment.kt where exception occuring
#AndroidEntryPoint
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
private val binding get() = _binding!!
#Inject
lateinit var sharedPref: SharedPreferences
#set:Inject
var isFirstAppOpen = true
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// inflate the layout and bind to the _binding
_binding = FragmentSetupBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(!isFirstAppOpen){
val navOptions = NavOptions.Builder()
.setPopUpTo(R.id.setupFragment, true)
.build()
findNavController().navigate(
R.id.action_setupFragment_to_runFragment,
savedInstanceState,
navOptions)
}
binding.tvContinue.setOnClickListener {
val success = writePersonalDataToSharedPref()
if (success){
findNavController().navigate(R.id.action_setupFragment_to_runFragment)
}else{
Snackbar.make(requireView(), "Please enter all the fields", Snackbar.LENGTH_SHORT).show()
}
}
}
private fun writePersonalDataToSharedPref(): Boolean {
val name = binding.etName.text.toString()
val weight = binding.etWeight.text.toString()
if(name.isEmpty() || weight.isEmpty()) {
return false
}
sharedPref.edit()
.putString(KEY_NAME, name)
.putFloat(KEY_WEIGHT, weight.toFloat())
.putBoolean(KEY_FIRST_TIME_TOGGLE, false)
.apply()
val toolbarText = "Let's go, $name!"
(requireActivity() as ActivityMainBinding).tvToolbarTitle.text = toolbarText
return true
}
}
I want to know exactly where I am making mistake
(requireActivity() as MainActivity).tvToolbarTitle.text = toolbarText
or
(requireActivity() as MainActivity).binding.tvToolbarTitle.text = toolbarText
error is with ActivityMainBinding this line add your activity name here like (MainActivity)
(requireActivity() as ActivityMainBinding)
This is the problem statement .. You can not cast Activity to a Binding Object because they are not related in any way ..
Although this is a wrong approach IMO . for now to make it work you can make your binding object public . and access it as below .
(requireActivity() as MainActivity).binding.tvToolbarTitle.text = toolbarText
For a better implementation you can use a SharedViewModel . Or have the toolbar in fragment itself if possible.

RecyclerView adapter doesnt execute any methods

I am working on a simple project. I am trying to show list of data but in adapter none of the methods are called
class PlacnikiAdapter(private val placniki: List<Placnik>) :
RecyclerView.Adapter<PlacnikiAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.ime)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(viewGroup.context).inflate(R.layout.layout_rv_item,
viewGroup, false)
return ViewHolder(inflater)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = placniki[position].ime
}
override fun getItemCount(): Int {
return placniki.size
}
class PlacnikiFragment : Fragment() {
private val TAG = "PlacnikiFragment"
private lateinit var binding: PlacnikiFragmentBinding
lateinit var viewModel: MainViewModel
private val retrofitService = RetrofitService.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Defines the xml file for the fragment
return inflater.inflate(R.layout.placniki_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = PlacnikiFragmentBinding.inflate(layoutInflater)
//get viewmodel instance using MyViewModelFactory
viewModel =
ViewModelProvider(this, ViewModelFactory(Repository(retrofitService))).get(
MainViewModel::class.java
)
//set recyclerview adapter
viewModel.placnikiList.observe(viewLifecycleOwner, {
Log.d(TAG, "placnikiList: $it")
binding.recyclerview.adapter = PlacnikiAdapter(it)
})
viewModel.errorMessage.observe(viewLifecycleOwner, {
Log.d(TAG, "errorMessage: $it")
})
viewModel.getVsiPlacniki()
}
I dont know what could be causing this. I changed activity to a fragment and beforehand everything worked normally and after changing to fragment recyclerview isnt showing items and the list isnt empty either so i dont pass list of zero items
I found the solution, the problem was in data binding. I had to add below code in gradle module
buildFeatures{dataBinding = true}

RecyclerView with coroutines, using Fragment doesn't populate data Kotlin

I am trying to work wih courutines and recycler view. I've done everything necessary to send requests to the API, but I still can't get the list that should be inside the recycler view. I am using fragment to create list and bottom nav. When I go to the fragment where my list should be, then I get the error: RecyclerView: No layout manager attached; skipping layoutand nothing more. I googled about this error and it says I should define the layout manager in xml, but it alreadt has layuout manager in my fragment
<androidx.recyclerview.widget.RecyclerView 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/list_item"
app:layoutManager="LinearLayoutManager"
Also my adapter and fragment for recycler view look like this:
class MyItemRecyclerViewAdapter(
) : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
var userNameResponse = mutableListOf<UsersNameItem>()
fun setNamesList(names: List<UsersNameItem>) {
this.userNameResponse = userNameResponse.toMutableList()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = userNameResponse[position]
holder.idView.text = item.id
holder.contentView.text = item.name
}
override fun getItemCount(): Int = userNameResponse.size
class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val idView: TextView = binding.itemNumber
val contentView: TextView = binding.content
}
}
Fragment:
class ItemFragment : Fragment() {
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_item_list, container, false)
}
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
super.onViewCreated(itemView, savedInstanceState)
layoutInflater.apply {
layoutManager = LinearLayoutManager(activity)
adapter = MyItemRecyclerViewAdapter()
}
}
companion object {
fun newInstance() = ItemFragment()
}
}
And MainActivity where I set up courutines and trying to make a request:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
lateinit var nameviewModel: ListNamesViewModel
val adapter = MyItemRecyclerViewAdapter()
///trying to create http-request for lists
val retrofitService = BasicApiService.getInstance()
val mainRepository = MainRepository(retrofitService)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
nameviewModel = ViewModelProvider(
this,
MyViewModelFactory(mainRepository)
)[ListNamesViewModel::class.java]
nameviewModel.userName.observe(
this, Observer {
adapter.setNamesList(it)
})
nameviewModel.message.observe(this) {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
nameviewModel.loadUsers()
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_dashboard,
R.id.navigation_notifications,
R.id.list_name_navig
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
Also I wanted to ask (I am very new to Kotlin nad just trying to make things work) if it is a good practice to leave request to API in the MainActivity ot I should do it in another way?
I see some confusion in your code, try to apply the next:
Why are you creating the layoutManager and adapter in the ItemFragment?
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
super.onViewCreated(itemView, savedInstanceState)
layoutInflater.apply {
layoutManager = LinearLayoutManager(activity)
adapter = MyItemRecyclerViewAdapter()
}
}
You need to move it and RecyclerView to your activity.
The using layoutInflater.apply {} doesn't make sense, it's doing nothing in your case. You need to set layoutManager and adapter to recyclerView then in the activity it should look like that (and rename list_item to recyclerView)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this)
adapter = MyItemRecyclerViewAdapter()
}
Remove app:layoutManager="LinearLayoutManager" from XML.
It looks like you don't need to use ItemFragment for your purpose because you use it just like a view and binding in the ViewHolder
class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val idView: TextView = binding.itemNumber
val contentView: TextView = binding.content
}
try changing in xml
app:layoutManager="LinearLayoutManager"
to
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"

why onClickListener does not work in my fragment activity?

Im new in programming, and i got stuck with adding onClickListener in my FragmentHome.kt
i added this code to my existing activity:
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
I tried set onClicklistener on a blank activity and it worked, but when i added it to an existing
Fragment activity it does nothing (it should display a toast with some text)
I see no error messages so i don't know where the problem could be.
Thank you for your responses.
enter code here
public class FragmentHome : Fragment() {
public class HomeFragmentElements : AppCompatActivity() {
private lateinit var spinView: Spinner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_home)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
spinView = findViewById(R.id.spinner)
spinView.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
}
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)
}
}
enter code here
Try to put your code into "onViewCreated":
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val exc = this.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
onViewCreated is executed after "onCreateView". View bindings and initializations should be into onViewCreated
change the following in your onCreateView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
collectId(view)
return view
}
fun collectId(view: view){
val exc = view.findViewById<Button>(R.id.execute)
exc.setOnClickListener {
Toast.makeText(this, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
You can consider viewBinding instead of findViewById
It looks like you have defined an AppCompatActivity subclass inside your Fragment class's definition, kind of like this:
class MyFragment: Fragment {
class MyActivity: AppCompatActivity() {
//...
}
}
The ability to define a non-inner class in a nested way is purely a code organization tool. There is absolutely no connection between your Fragment and Activity classes here. The code above is no different than if you did this, with each class in a completely different file:
class MyFragment: Fragment {
}
class MyActivity: AppCompatActivity() {
}
It also doesn't make sense to think of an Activity as a child of a Fragment. A Fragment is a piece of an Activity, so it's the other way around.
All the code that you have in this Activity's onCreate should be put in the Fragment's onViewCreated() function, except for the call to setContentView(), because that's what your overriding of onCreateView() does. And remove the unused nested Activity class.
Also, you don't need to override onCreateView if you're simply inflating a layout and returning it. You can put the layout directly in the super-constructor call like this:
public class FragmentHome : Fragment(R.layout.fragment_home) {
private lateinit var spinView: Spinner
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// your view setup code
}
}

How can i fix the error 'java.lang.IllegalStateException: RecyclerView must not be null'

I have a recyclerView where I send data with my adapter but got the error
RecyclerView must not be null
code with my adpater:
class InWaitingFragment : Fragment() {
private lateinit var adapter: FastItemAdapter<BarterWaitingItem>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
println("CONTAINER: " + container)
val inflate: FrameLayout = inflater.inflate(R.layout.fragment_in_waiting, container, false) as FrameLayout
adapter = FastItemAdapter<BarterWaitingItem>()
waitingRecyclerView.layoutManager = LinearLayoutManager(this)
waitingRecyclerView.adapter = adapter
val retrofit = Retrofit.Builder()
.baseUrl("http://10.93.182.95:8888")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(RequestManager::class.java)
val action = service.getPending()
action.enqueue(object: Callback<ArrayList<GetBarterResponse>> {
override fun onResponse(
call: Call<ArrayList<GetBarterResponse>>,
response: Response<ArrayList<GetBarterResponse>>
) {
val allBarter = response.body()
if(allBarter != null){
for (c in allBarter){
println("OBJET: ${c.buyer_object.title}")
}
println("ONRESPONSE En attente: " + response.body().toString())
}
}
override fun onFailure(call: Call<ArrayList<GetBarterResponse>>, t: Throwable) {
println("ONFAILURE En attente: " + t.toString())
}
})
return inflate
}
}
got an error to on the this of LinearLayoutManager(this), says:
`require:Context!
Founds: InWaitingFragment
You should change LinearLayoutManager(this) to LinearLayoutManager(this.context)
For your LinearLayoutManager, Fragments aren't extending Context so you cant use this as parameters. Instead, use this:
waitingRecyclerView.layoutManager = LinearLayoutManager(context!!)
For the runtime error, "RecyclerView must not be null", it's because you're accessing the properties of the waitingRecyclerView inside the onCreateView callback. The layout hasn't been initialized yet. You can move your initialization of waitingRecyclerView to the 'onViewCreated' callback.
If you must initialize the waitingRecyclerView inside onCreateView you can access the waitingRecyclerView via the object you created when inflating the layout, i.e. inflate:
inflate.waitingRecyclerView.layoutManager = LinearLayoutManager(context!!)
inflate.waitingRecyclerView.adapter = adapter