I have a Fragment that I would Like to have twice in the App, but with slight changes. Is there a way to use some kind of Abstract Class? I already tried to find a solution my self, but I cant find a way to get the Viewmodel from the Activity using delegated properties as Android Studio is saying the can't be abstract. Another problem that I'm facing is the Arguments I'm passing to the Fragment.
(To clarify: The Property that should change is the Viewmodel; I'd like to have another kind of Viewmodel for the second Fragment. Also, the Viewmodel I use in the Code below and the other Viewmodel both inherit from the same class, so switching the Type shouldn't be that big of a problem)
Here's the Code of the Fragment:
package net.informatikag.thomapp.viewables.fragments.ThomsLine.main
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.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.android.volley.*
import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.Volley
import com.google.android.material.snackbar.Snackbar
import net.informatikag.thomapp.MainActivity
import net.informatikag.thomapp.R
import net.informatikag.thomapp.databinding.ThomslineMainFragmentBinding
import net.informatikag.thomapp.utils.handlers.WordpressRecyclerAdapter
import net.informatikag.thomapp.utils.ArticleListSpacingDecoration
import net.informatikag.thomapp.utils.models.ArticleClickHandler
import net.informatikag.thomapp.utils.models.data.WordpressArticle
import net.informatikag.thomapp.utils.models.data.WordpressPage
import net.informatikag.thomapp.utils.models.view.ThomsLineViewModel
import java.util.*
import kotlin.collections.ArrayList
/**
* Pulls a list of articles from the JSON API of the Wordpress instance of the ThomsLine student newspaper.
* The articles are dynamically loaded with a RecyclerView.
*/
class ThomsLineFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener, ArticleClickHandler {
private var _binding: ThomslineMainFragmentBinding? = null // Verweis zum Layout
private val viewModel: ThomsLineViewModel by activityViewModels() // Das Viewmodel in dem die wichtigen Daten des Fragments gespeichert werden
private lateinit var recyclerAdapter: WordpressRecyclerAdapter // Hier werden die Artikel angezeigt
private lateinit var swipeRefreshLayout: SwipeRefreshLayout // wird benutz um die Artikel neu zu laden
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
/**
* Will be executed when the fragment is opened
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate Layout
_binding = ThomslineMainFragmentBinding.inflate(inflater, container, false)
val root: View = binding.root
//Instantiate Variables
recyclerAdapter = WordpressRecyclerAdapter(this, viewModel)
//Add Observer to articles to update Recyclerview
viewModel.articles.observe(viewLifecycleOwner, Observer {
swipeRefreshLayout.isRefreshing = false
})
//region Init SwipeRefresh Layout
swipeRefreshLayout = root.findViewById(R.id.thomsline_swipe_container)
swipeRefreshLayout.setOnRefreshListener(this)
swipeRefreshLayout.setColorSchemeResources(
R.color.primaryColor,
R.color.secondaryColor
)
if(viewModel.isEmpty()) {
swipeRefreshLayout.post {
// Display Refresh Indicator
swipeRefreshLayout.isRefreshing = true
// Load First Article Page
loadArticles(0, true)
}
}
//endregion
//region Init Recycler View
_binding?.thomslineRecyclerView?.apply {
layoutManager = LinearLayoutManager(this#ThomsLineFragment.context)
addItemDecoration(ArticleListSpacingDecoration())
adapter = recyclerAdapter
}
//endregion
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
/**
* Called when the SwipeRefresh Layout is triggerd
*/
override fun onRefresh() {
loadArticles(0, true)
}
/**
* Loads all Article pages until "page" and removes all cached pages after it
*/
fun loadArticles(page:Int, reloadAll: Boolean){
// Remove all cached pages after the given one
if(page == 0) {
viewModel.removeArticlePagesFromIndex(1, recyclerAdapter)
viewModel.lastPage = -1
}
// Create a new Request Queue
val requestQueue = Volley.newRequestQueue(this.context)
// Add requests to load the Pages to the requestQueue
if(reloadAll)
for (i in 0 until page+1) {
reloadPage(i, requestQueue)
}
else reloadPage(page)
}
// Reload a page without a given Request Queue
fun reloadPage(id:Int){
reloadPage(id, Volley.newRequestQueue(this.context))
}
// Reload a Page while adding the Requests to a given Request Queue
fun reloadPage(id: Int, requestQueue:RequestQueue) {
Log.d("ThomsLine", "Requesting Data for page $id")
// Start the Request
requestQueue.add(JsonArrayRequest(viewModel.BASE_URL + MainActivity.WORDPRESS_BASE_URL_LITE + "&&page=${id+1}",
{ response ->
Log.d("ThomsLine", "Got Data for page $id")
// A Variable to load the Articles to
val data = ArrayList<WordpressArticle>()
// Load the Articles from the JSON
for (j in 0 until response.length()) data.add(WordpressArticle(response.getJSONObject(j), true, viewModel.BASE_URL))
// Update the RecyclerView
viewModel.setArticlePage(id, WordpressPage(data.toTypedArray()), recyclerAdapter)
},
{ volleyError ->
Log.d("ThomsLine", "Request Error while loading Data for page $id")
// Check if the Error is caused because loading a non Existing Page
if (volleyError.networkResponse?.statusCode == 400){
// Update the Last Page Variable
viewModel.lastPage = if(id-1<viewModel.lastPage) viewModel.lastPage else id-1
recyclerAdapter.notifyItemChanged(recyclerAdapter.itemCount-1)
Log.d("ThomsLine", "Page does not exist (last page: ${viewModel.lastPage})")
} else {
Log.d("ThomsLine", "Request failed: ${volleyError.message.toString()}")
// Display a Snackbar, stating the Error
Snackbar.make(requireActivity().findViewById(R.id.app_bar_main), WordpressArticle.getVolleyError(volleyError, requireActivity()), Snackbar.LENGTH_LONG).show()
}
//recyclerAdapter.notifyItemChanged(id)
}
))
}
/**
* Called when a Article is clicked
*/
override fun onItemClick(wordpressArticle: WordpressArticle) {
val action = ThomsLineFragmentDirections.actionNavThomslineToNavThomslineArticleView(wordpressArticle.id)
findNavController().navigate(action)
}
}
You don't assign the ViewModel in your abstract class (which is what you're doing by using a delegate, by activityViewModels()). Just make it an abstract property in your abstract class, and then your concrete Fragment subclasses will be required to assign a value to it (using by activityViewModels() if you like):
abstract class BaseFragment : Fragment() {
...
abstract val viewModel: BaseViewModel
...
}
class RealFragment : BaseFragment() {
...
// you can specify a subtype of BaseViewModel in your implementation
override val viewModel: SomeViewModel by activityViewModels()
...
}
Related
I am new to Android app dev,I want my fragment to store user information in a viewModel, yet that model is accessed by other fragments. The app runs well but I would like it to store the viewModel even if the app is destroyed. How can I save the state of my model?
This is my viewModel class (it starts with the package name):
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
/**
* This model is shared by related views.
* stores user name1 and name2 that prevents repetition in filling forms.
* stores data lost during configuration changes.
* stores data from the form.
* */
class SharedViewModel() : ViewModel() {
private val _nameOne = MutableLiveData<String>()
val nameOne: LiveData<String> = _nameOne
private val _nameTwo = MutableLiveData<String>()
val nameTwo: LiveData<String> = _nameTwo
private val _emailAddress = MutableLiveData<String>()
val emailAddress: LiveData<String> = _emailAddress
private val _phoneNumber = MutableLiveData<String>()
val phoneNumber: LiveData<String> = _phoneNumber
private val _nationalIdNumber = MutableLiveData<String>()
val nationalIdNumber: LiveData<String> = _nationalIdNumber
fun saveUserData(
nameA: String, nameB: String,
emailA: String, phoneN: String,
natId: String) {
_nameOne.value = nameA
_nameTwo.value = nameB
_emailAddress.value = emailA
_phoneNumber.value = phoneN
_nationalIdNumber.value = natId
}
}
This is the MainActivity class (it starts with the package name):
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
import ke.co.qaizen.qaizencarrental.databinding.ActivityMainBinding
/**
* Main Activity and entry point for the app.
*/
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.sleep(3)
installSplashScreen()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Get the navigation host fragment from this Activity
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
// Instantiate the navController using the NavHostFragment
navController = navHostFragment.navController
// Make sure actions in the ActionBar get propagated to the NavController
setupActionBarWithNavController(navController)
}
/**
* Enables back button support. Simply navigates one element up on the stack.
*/
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
And this is my fragment class (it also starts with the package name):
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import ke.co.qaizen.qaizencarrental.databinding.FragmentAccountBinding
import ke.co.qaizen.qaizencarrental.mainmodel.SharedViewModel
class AccountFragment : Fragment() {
private var _binding: FragmentAccountBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAccountBinding.inflate(inflater, container, false)
sharedViewModel.nameOne.observe(viewLifecycleOwner) { nameOne ->
binding.userNameOne.text = nameOne
}
sharedViewModel.nameTwo.observe(viewLifecycleOwner) { nameTwo ->
binding.userNameTwo.text = nameTwo
}
sharedViewModel.emailAddress.observe(viewLifecycleOwner) { emailAddress ->
binding.userEmailAddress.text = emailAddress
}
sharedViewModel.phoneNumber.observe(viewLifecycleOwner) { phoneNumber ->
binding.userPhoneNumber.text = phoneNumber
}
sharedViewModel.nationalIdNumber.observe(viewLifecycleOwner) {
nationalIdNumber ->
binding.userIdNumber.text = nationalIdNumber
}
return binding.root
}
}
Try the Google Room DB library.
Easy store objects.
Can someone help me on how to transfer data from one recyclerview fragment to another recyclerview fragment?
This is my newly created CartRecyclerAdapter.kt for my cart recyclerview from a fragment. The main idea of SubmitItem() is to accept the selected item in the Itemrecyclerview.
package com.example.karawapplication
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.example.karawapplication.models.ItemPost
import kotlinx.android.synthetic.main.layout_item_cart_list.view.*
import kotlinx.android.synthetic.main.layout_item_list_item.view.*
class CartRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var Items : List<ItemPost> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ItemRecyclerAdapter.ItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.layout_item_cart_list, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder)
{
is ItemRecyclerAdapter.ItemViewHolder ->
{
holder.bind(Items.get(position))
}
}
}
override fun getItemCount(): Int {
return Items.size
}
fun SubmitItem(Item : ItemPost)
{
Items.toMutableList().add(Item)
}
class ItemViewHolder constructor(
Itemview : View
): RecyclerView.ViewHolder(Itemview)
{
val ItemImage : ImageView = Itemview.Item_image_cart
val ItemTitle : TextView = Itemview.Item_title_cart
val ItemCategory: TextView = Itemview.Item_category_cart
val ItemPrice : TextView = Itemview.Item_price_cart
fun bind (itempost : ItemPost)
{
ItemTitle.setText(itempost.Itemtitle)
ItemCategory.setText(itempost.ItemCategory)
ItemPrice.setText(itempost.ItemPrice)
val requestOptions = RequestOptions()
.placeholder(R.drawable.ic_baseline_brush_24)
.error(R.drawable.ic_baseline_brush_24)
Glide.with(itemView.getContext())
.applyDefaultRequestOptions(requestOptions)
.load(itempost.image)
.into(ItemImage)
}
}
}
This is where I use the SubmitItem() in my ItemRecyclerAdapter.kt which contains items of my shop app and displayed from another fragment. CartAdapter is the adapter of the cart that I recently created in order to access the function SubmitItem().
ItemButton.setOnClickListener()
{
Toast.makeText(itemView.context, "${itempost.image}, ${itempost.Itemtitle}, ${itempost.ItemPrice}", Toast.LENGTH_SHORT).show()
CartAdapter.SubmitItem(ItemPost(itempost.image,itempost.Itemtitle,itempost.ItemCategory,itempost.ItemPrice))
}
This is my code for my fragments
ShopFragment.kt contains the recyclerview of my items.
package com.example.karawapplication.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.android.volley.Request
import com.android.volley.VolleyError
import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.Volley
import com.example.karawapplication.ItemRecyclerAdapter
import com.example.karawapplication.R
import com.example.karawapplication.models.ItemPost
import kotlinx.android.synthetic.main.fragment_shop.*
import org.json.JSONException
import org.json.JSONObject
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [Shop.newInstance] factory method to
* create an instance of this fragment.
*/
class Shop : Fragment(){
// TODO: Rename and change types of parameters
private lateinit var ItemAdapter : ItemRecyclerAdapter
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_shop, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
addDataSet()
}
// CUSTOM FUNCTIONS
private fun addDataSet()
{
createDataset {
list -> ItemAdapter.SubmitList(list)
}
}
private fun initRecyclerView()
{
ShopRecycleView.layoutManager = LinearLayoutManager(activity)
ItemAdapter = ItemRecyclerAdapter()
ShopRecycleView.adapter = ItemAdapter
//Toast.makeText(context, "Recycler Trigger", Toast.LENGTH_SHORT).show()
}
// https://tutorials.eu/json-parsing-and-how-to-use-gson-in-android/
// Generates the data for the recycleview
fun createDataset(onSuccess: (List<ItemPost>) -> Unit){
val url = "http://api.karawcraftventure.com/item"
// LIST DATA VARIABLE FOR RECYCLEVIEW
val list = ArrayList<ItemPost>()
// VOLLEY API REQUEST
val Queue = Volley.newRequestQueue(activity)
val jsonObject = JsonArrayRequest(
Request.Method.GET,url,null,
{response ->
try
{
for (i in 0 until response.length())
{
val item : JSONObject = response.getJSONObject(i)
val API_Image : String = item.getString("product_url_image")
val API_ItemName : String = item.getString("product_name")
val API_Price : String = item.getString("product_price")
val API_Category : String = item.getString("product_category")
// Toast Notif if data is extracted or not
//Toast.makeText(context, "$API_ItemName - $API_Price - $API_Category", Toast.LENGTH_SHORT).show()
list.add(ItemPost(API_Image, API_ItemName, API_Category, API_Price))
}
onSuccess(list)
}
catch (e: JSONException)
{
e.printStackTrace()
}
},
{ error: VolleyError? -> Toast.makeText(context, error?.message.toString(), Toast.LENGTH_SHORT).show()
}
)
Queue.add(jsonObject)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment Shop.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
Shop().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Cart.kt fragment on the other hand, contains my shopping cart.
package com.example.karawapplication.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.karawapplication.CartRecyclerAdapter
import com.example.karawapplication.ItemRecyclerAdapter
import com.example.karawapplication.R
import kotlinx.android.synthetic.main.fragment_cart.*
import kotlinx.android.synthetic.main.fragment_shop.*
import kotlinx.android.synthetic.main.fragment_shop.ShopRecycleView
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [Cart.newInstance] factory method to
* create an instance of this fragment.
*/
class Cart : Fragment() {
// TODO: Rename and change types of parameters
private lateinit var ItemAdapter : CartRecyclerAdapter
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_cart, container, false)
}
private fun initRecyclerView()
{
Cart_RecyclerView.layoutManager = LinearLayoutManager(activity)
ItemAdapter = CartRecyclerAdapter()
Cart_RecyclerView.adapter = ItemAdapter
//Toast.makeText(context, "Recycler Trigger", Toast.LENGTH_SHORT).show()
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment Cart.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
Cart().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
The code has no errors, but it does not show any output in my cart recyclerview fragment.
(I'm going to follow the "starts with a lowercase letter" convention for variables and functions here, because I'm tired and it's less confusing)
Your submitItem function just creates a new copy of items (items.toMutableList()) and adds to that. Then it's immediately discarded when you exit the function.
All your adapter code that handles your data (getItemCount, onBindViewHolder) references items, so you need to update that instead, and let the adapter know it's changed:
fun submitItem(item: ItemPost) {
// plus creates a copy, but adds the new item without making the result mutable
items = items.plus(item)
// we also need to tell the adapter that the data has changed, so it can
// update - there are more efficient calls (you'll probably get a lint
// warning telling you to use one) but this is the simplest
notifyDataSetChanged()
}
This is just making your adapter update correctly - I'm assuming you've got your communication between the two fragments set up properly (communicating through the parent activity, passing one fragment's adapter into the other, using a view model). Have a read of this if you need to: Communicating with fragments
I am trying to display items on a recyclerview but it says
"No adapter attached; skipping layout"
.
I can't figure out the error. I tried with Activities instead of Fragements and it works super well. I am not yet so familiar with Framents. Kindly help.
RecyclerAdapter
package com.manzugerald.shukuruyesu.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.HymnDetailActivity
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsItemAdapter(
val context: Context,
val dataset: List<Hymns>
) : RecyclerView.Adapter<HymnsItemAdapter.ItemViewHolder>() {
//provide a reference to the views for each data item (in this case there is only one item)
//Complex data items may need more than one view per item, and
//you provide access to all the views for a data item in a view holder
//Each data item is just an Affirmation object
inner class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
//val hymn_imageView: ImageView =view.findViewById(R.id.item_image)
val hymn_title: TextView = view.findViewById(R.id.textView_hymn_title)
val hymn_language: TextView = view.findViewById(R.id.textView_hymn_language)
val hymn_number: TextView = view.findViewById(R.id.textView_hymn_number)
var hymn_detail: TextView = view.findViewById(R.id.textView_hymn_text)
//This has been unused. I will try to see how best to use it in the future
var hymnPosition = 0
/**
* //So as the contents of the views displayed are clickable, append the setOnClickListener to the view holder rather than individual views
* An explicit intent is used for exchanging information between two or more fragements or activities (screen)
* The information to be carried to the next screen is passed as a key-value pair
* The key is retrieved in the receiving activity or fragment
* Then the data the key carries is gotten and assigned to the views so as to be viewed
*/
init {
view.setOnClickListener {
val intent = Intent(context,HymnDetailActivity::class.java)
val message = hymn_number.text.toString()
val message_two = hymn_language.text.toString()
val message_three = hymn_title.text.toString()
val message_four = hymn_detail.text.toString()
intent.putExtra("key_one", message)
intent.putExtra("key_two",message_two)
intent.putExtra("key_three",message_three)
intent.putExtra("key_four",message_four)
context.startActivity(intent)
}
}
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context).inflate(R.layout.list_item_hymn_list, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
/**
* Binds or attaches data to the views.
* Does this by interacting with the itemViewHolder
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
//holder.hymn_imageView.setImageResource(item.imageResourceId)
holder.hymn_title.text = context.resources.getString(item.titleStringResourceId)
holder.hymn_language.text = context.resources.getString(item.languageStringResourceId)
holder.hymn_number.text = "Song Number: " + context.resources.getString(item.numberStringResourceId)
holder.hymn_detail.text = context.resources.getString(item.detailStringResourceId)
holder.hymnPosition = position
holder.hymn_detail.isVisible=false
}
/**
* Return the size of the dataset (invoked by the layout manager)
*/
//Takes not of the size of the data, number of rows so that binding can be done with ease
override fun getItemCount(): Int = dataset.size
}
The data source:
package com.manzugerald.shukuruyesu.data
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsDataSource {
//Gets the data from the string resources
fun loadHymns():List<Hymns>{
return listOf<Hymns>(
Hymns(R.string.affirmation1,R.string.affirmationNum1,R.string.affirmationLang1,R.string.affirmationDetail1),
Hymns(R.string.affirmation2,R.string.affirmationNum2,R.string.affirmationLang2,R.string.affirmationDetail2),
Hymns(R.string.affirmation3,R.string.affirmationNum3,R.string.affirmationLang3,R.string.affirmationDetail3),
Hymns(R.string.affirmation4,R.string.affirmationNum4,R.string.affirmationLang4,R.string.affirmationDetail4),
Hymns(R.string.affirmation5,R.string.affirmationNum5,R.string.affirmationLang5,R.string.affirmationDetail5),
Hymns(R.string.affirmation6,R.string.affirmationNum6,R.string.affirmationLang6,R.string.affirmationDetail6),
Hymns(R.string.affirmation7,R.string.affirmationNum7,R.string.affirmationLang7,R.string.affirmationDetail7),
Hymns(R.string.affirmation8,R.string.affirmationNum8,R.string.affirmationLang8,R.string.affirmationDetail8),
Hymns(R.string.affirmation9,R.string.affirmationNum9,R.string.affirmationLang9,R.string.affirmationDetail9),
Hymns(R.string.affirmation10,R.string.affirmationNum10,R.string.affirmationLang10,R.string.affirmationDetail10)
)
}
}
The Data class:
package com.manzugerald.shukuruyesu.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class Hymns(
#StringRes var titleStringResourceId: Int,
#StringRes var numberStringResourceId: Int,
#StringRes var languageStringResourceId: Int,
#StringRes var detailStringResourceId: Int
// #DrawableRes val imageResourceId: Int
)
The Fragment to display the recyclerview:
package com.manzugerald.shukuruyesu
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.data.HymnsDataSource
import com.manzugerald.shukuruyesu.databinding.FragmentHymnListBinding
import com.manzugerald.shukuruyesu.adapter.HymnsItemAdapter
import com.manzugerald.shukuruyesu.model.Hymns
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [HymnListFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class HymnListFragment : Fragment() {
// TODO: Rename and change types of parameters
//enable and refrence binding
private var _binding: FragmentHymnListBinding? = null
private val binding get() = _binding!!
//property for the recycler view
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
//To inflate the layout, call teh onCreatView Method
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHymnListBinding.inflate(inflater,container,false)
val view = binding.root
return view
}
//Bind the views in onViewCreated
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val myDataSource = HymnsDataSource().loadHymns()
recyclerView = binding.recyclerView
recyclerView.apply {
layoutManager=LinearLayoutManager(requireContext())
adapter= HymnsItemAdapter(dataset = myDataSource, context = requireContext())
adapter= HymnsItemAdapter(context,myDataSource)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding=null
}
}
HymnListFragment Layout File:
<?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=".HymnListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_View"
android:layout_width="0dp"
android:layout_height="0dp"
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/list_item_hymn_list"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I got the solution to the question...
Neither the Adapter nor the Fragments had a problem.
The problem was in my MainActivity.kt file, which I didn't upload here...
The error came about when I tried changing from activities to Fragments....
Initially, I forgot to take off or comment the inflation of the layouts (Layouts are inflated in the respective Fragments, not the main activity).
For illustration purposes, I will comment the said error in the code.
My bad!
Main Activity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
}
}
I'm following UDACITY free course on developing Android apps with Kotlin and I'm actually at the Viewmodel/MVVM part of the lesson, ie implementing Viewmodel classes for a better separation of concerns. So anyway, Im blocked right now. The exercise I'm on is about creating the Viewmodel class and transferring variables and functions from the Fragment class to this newly created class. I follow the tutorial step by step, check the correct answer on the provided git diff and I still find myself blocked by Unresolved reference errors.
Before changing the code, i had to update my Gradle module file to use ViewModel
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
Then I had to declare my Viewmodel object in my Fragment class
gameViewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
ViewModelProviders being deprecated, old course, I had to change, after search, to
gameViewModel = ViewModelProvider(this).get(GameViewModel::class.java)
It seems to be the right way to do, but I'm still left with some Unresolved references on the variables word (gameViewModel.word) and score (gameViewModel.score) in the Fragment class, unable to compile. I dont know if I declared the Viewmodel object correctly or if I'm missing something else...
I dont have this problem, Unresolved reference, with my ViewModel class functions, ie gameViewModel.onCorrect() and gameViewModel.onSkip(). They seem to be properly declared and called in the Fragment class, which begs me the question on my variables, word and score...
My Fragment class
package com.example.android.guesstheword.screens.game
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.NavHostFragment.findNavController
import com.example.android.guesstheword.R
import com.example.android.guesstheword.databinding.GameFragmentBinding
import timber.log.Timber
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var gameViewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Timber.i("onCreateView GameFragment called")
gameViewModel = ViewModelProvider(this).get(GameViewModel::class.java)
Timber.i("ViewModelProvider is called")
binding.correctButton.setOnClickListener {
gameViewModel.onCorrect()
updateScoreText()
updateWordText()
}
binding.skipButton.setOnClickListener {
gameViewModel.onSkip()
updateScoreText()
updateWordText()
}
updateScoreText()
updateWordText()
return binding.root
}
/**
* Called when the game is finished
*/
fun gameFinished() {
val action = GameFragmentDirections.actionGameToScore(gameViewModel.score)
findNavController(this).navigate(action)
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = gameViewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = gameViewModel.score.toString()
}
override fun onDestroyView() {
super.onDestroyView()
Timber.i("onDestroyView GameFragment called")
}
}
My ViewModel class
package com.example.android.guesstheword.screens.game
import androidx.lifecycle.ViewModel
import timber.log.Timber
var word = ""
var score = 0
private lateinit var wordList: MutableList<String>
class GameViewModel: ViewModel() {
init {
Timber.i("GameViewModel is created")
resetList()
nextWord()
}
override fun onCleared() {
super.onCleared()
Timber.i("GameViewModel is cleared")
}
/**
* Resets the list of words and randomizes the order
*/
fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
//Select and remove a word from the list
if (wordList.isEmpty()) {
//gameFinished()
} else {
word = wordList.removeAt(0)
}
}
/** Methods for buttons presses **/
fun onSkip() {
score--
nextWord()
}
fun onCorrect() {
score++
nextWord()
}
}
Where did I screw up ?
The variables you're trying to access aren't part of the same scope:
var word = ""
var score = 0
private lateinit var wordList: MutableList<String>
class GameViewModel: ViewModel() {
...
your variables are declared outside of the ViewModel, add them inside the class GameViewModel to make them instance variables:
class GameViewModel: ViewModel() {
var word = ""
var score = 0
...
}
folks. I have been working on a project with kotlin and I need to make a fragment that comunicate with the parent activity... I followed exactly what google and other websites suggested but I still get an error "activity does not override anything"... All of the other solutions are not working for me... here is the code .
FRAGMENT
package com.me.assistan.assistant
import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.LinearLayout
import android.widget.ToggleButton
import kotlinx.android.synthetic.main.content_newplan.*
import java.util.*
class addingTask : Fragment(), View.OnClickListener{
var func = Functions
var globalTask = GlobalTask
private lateinit var listener: OnTimeSettedListener
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is OnTimeSettedListener) {
listener = context
} else {
throw ClassCastException(context!!.toString() + " must implement
OnTimeSettedListener.")
}
}
companion object {
fun newInstance(): addingTask {
return addingTask()
}
}
override fun onCreateView(inflater: LayoutInflater?, container:
ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_adding_task,
container,
false)
val activity = activity
view.theTime.setOnClickListener { v ->
listener.onTimeSetListtedener("test")
}
return view
}
interface OnTimeSettedListener{
fun onTimeSetListtedener(comic : String){
println("ok")
}
}
}// Required empty public constructor
And not the MAIN ACTIVITY
class Newplan : AppCompatActivity(), addingTask.OnTimeSettedListener {
var posx = 0f
private var startx = 0f
private var posy = 0f
private var starty = 0f
var backIntent = Intent();
var func = Functions
var globalTask = GlobalTask
val fragment = addingTask()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_newplan)
if(savedInstanceState === null){
var args = Bundle()
supportFragmentManager.beginTransaction()
.add(R.id.taskMain, addingTask.newInstance(),"newTask")
.commit()
}
}
override fun onTimeSettedListener(comic : String){
println("params")
}
}
I get the error on the activity class... When I remove the "override?, the error is gone but nothing happen when I click on the button... What am I doing wrong?
I think you shouldn't add method body to your interface method. It is not allowed in Java. In Kotlin there is no error that showing method body is restricted. But you should remove body. Change your interface like
interface OnTimeSettedListener{
fun onTimeSetListtedener(comic : String)
}
Also actually you are not overriding your Listener's method. Method name in OnTimeSettedListener is onTimeSetListtedener but you are overriding onTimeSettedListener which is not really exist in your code.
Also as #albodelu mentioned answer and #chris mentioned in comments, you cannot write methods in methods. It is not correct usage.
As #chris commented, you need to move the lines below outside of onCreate() method:
override fun onTimeSettedListener(comic: String) {
println("params")
}
You also need to match names replacing
interface OnTimeSettedListener {
fun onTimeSetListtedener(comic : String){
println("ok")
}
}
by
interface OnTimeSettedListener {
fun onTimeSettedListener(comic: String) {
println("ok")
}
}
Update
If you fix the name typo and remove the default implementation of the onTimeSettedListener declaration in the interface inside your fragment, as your activity implements it and Android Studio warns you about the missing override, it's possible to click it and select that the IDE implements it for you to avoid errors doing it:
interface OnTimeSettedListener{
fun onTimeSettedListener(comic : String)
}
You also need to fix the call replacing:
listener.onTimeSetListtedener("test")
by
listener.onTimeSettedListener("test")