Getting an error message that i am not familiar with doing the dogglers app for the google developers unit 2 app - kotlin

heres the link for the project . -> https://developer.android.com/codelabs/basic-android-kotlin-training-project-dogglers-app?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-3%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-project-dogglers-app#2
package com.example.dogglers.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.dogglers.R
import com.example.dogglers.const.Layout
import com.example.dogglers.const.Layout.GRID
import com.example.dogglers.data.DataSource
import com.example.dogglers.data.DataSource.dogs
import com.example.dogglers.model.Dog
import kotlinx.coroutines.selects.select
/*** Adapter to inflate the appropriate list item layout and populate the view with information* from the appropriate data source*/
class DogCardAdapter(
private val context: Context?,
private val layout: Int
): RecyclerView.Adapter<DogCardAdapter.DogCardViewHolder>() {
val dogs = DataSource.dogs
// TODO: Initialize the data using the List found in data/DataSource
/*** Initialize view elements*/
class DogCardViewHolder(view: View?) : RecyclerView.ViewHolder(view!!) {
val dogImageView: ImageView? = view?.findViewById(R.id.pics_of_dogs)
val dogNameText:TextView? = view?.findViewById(R.id.dogs_name)
val dogAgeText:TextView? = view?.findViewById(R.id.dogs_age)
val dogHobbyText:TextView?=view?.findViewById(R.id.dogs_hobbies)
// TODO: Declare and initialize all of the list item UI components
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DogCardAdapter.DogCardViewHolder {
// TODO: Use a conditional to determine the layout type and set it accordingly.
// if the layout variable is Layout.GRID the grid list item should be used. Otherwise the
// the vertical/horizontal list item should be used.
val adapterLayout = when (viewType) {
Layout.GRID -> LayoutInflater.from(parent.context).inflate(R.layout.grid_list_item,parent,false)
else -> LayoutInflater.from(parent.context).inflate(R.layout.vertical_horizontal_list_item,parent,false)
}
return DogCardViewHolder(adapterLayout)
// TODO Inflate the layout
}
// TODO: Null should not be passed into the view holder. This should be updated to reflect
// the inflated layout.
}
override fun getItemCount(): Int {
return dogs.size
}
override fun onBindViewHolder(holder: DogCardAdapter.DogCardViewHolder, position: Int) {
// TODO: Get the data at the current position
// TODO: Set the image resource for the current dog
// TODO: Set the text for the current dog's name
// TODO: Set the text for the current dog's age
val dogData = dogs[position]
holder.dogImageView?.setImageResource(dogData.imageResourceId)
holder.dogNameText?.text= dogData.name
val resources = context?.resources
holder.dogAgeText?.text= resources?.getString(R.string.dog_age,dogData.age)
holder.dogHobbyText?.text = resources?.getString(R.string.dog_hobbies,dogData.hobbies)
// TODO: Set the text for the current dog's hobbies by passing the hobbies to the
// R.string.dog_hobbies string constant.
// Passing an argument to the string resource looks like:
// resources?.getString(R.string.dog_hobbies, dog.hobbies)
}
}
//fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DogCardAdapter.DogCardViewHolder

Related

How to set default card without any selection

I am developing e-commerce app in which users can set multiple options for address. Which by default they have to select every time. Each address have a separate address id which helps in identification of address selected. To reduce extra click of users i want to make first address i.e. address id present on position 0 as default selected address. Different address are present in different material card view so you can easily select one of them. My app screen shot is present as below.
enter image description here
My code is as following
import android.content.Context
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import com.sbhs.stone.R
import com.sbhs.stone.data.UserData
import com.sbhs.stone.databinding.LayoutAddressCardBinding
import com.sbhs.stone.ui.getCompleteAddress
private const val TAG = "AddressAdapter"
class AddressAdapter(
private val context: Context,
addresses: List<UserData.Address>,
private val isSelect: Boolean,
) :
RecyclerView.Adapter<AddressAdapter.ViewHolder>() {
lateinit var onClickListener: OnClickListener
var data: List<UserData.Address> = addresses
var lastCheckedAddress: String? = null
private var lastCheckedCard: MaterialCardView? = null
var selectedAddressPos = -1
inner class ViewHolder(private var binding: LayoutAddressCardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(address: UserData.Address, position: Int) {
binding.addressCard.isChecked = position == selectedAddressPos
binding.addressPersonNameTv.text =
context.getString(R.string.person_name, address.fName, address.lName)
binding.addressCompleteAddressTv.text = getCompleteAddress(address)
binding.addressMobileTv.text = address.phoneNumber
if (isSelect) {
binding.addressCard.setOnClickListener {
onCardClick(position, address.addressId, it as MaterialCardView)
}
}
//binding.addressCard.setOnFocusChangeListener { view: View, b: Boolean ->
// onCardClick(0, address.addressId, 0 as MaterialCardView)
//}
binding.addressEditBtn.setOnClickListener {
onClickListener.onEditClick(address.addressId)
}
binding.addressDeleteBtn.setOnClickListener {
onClickListener.onDeleteClick(address.addressId)
notifyDataSetChanged()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutAddressCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(data[position], position)
}
override fun getItemCount(): Int = data.size
interface OnClickListener {
fun onEditClick(addressId: String)
fun onDeleteClick(addressId: String)
}
private fun onCardClick(position: Int, addressTd: String, card: MaterialCardView) {
if (addressTd != lastCheckedAddress) {
card.apply {
strokeColor = context.getColor(R.color.blue_accent_300)
isChecked = true
strokeWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
2F,
resources.displayMetrics
).toInt()
}
lastCheckedCard?.apply {
strokeColor = context.getColor(R.color.light_gray)
isChecked = false
strokeWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
1F,
resources.displayMetrics
).toInt()
}
lastCheckedAddress = addressTd
lastCheckedCard = card
selectedAddressPos = position
Log.d(TAG, "onCardClick: selected address = $addressTd")
}
}
}
So in above code i want to make selectedAddressPos = 0, & last checked Address as addressTd of address present on position 0. Remember my address can be null so this condition should only applied when address is non-null. Please help me i am new in coding. The information about user address are stored in firebase. so i cant make any card as default from xml.
I tried to make selectedAddressPos as 0 at time of initiation but it will only show it in app and no address id is taken.

UninitializedPropertyAccessException in Android Studio using Kotlin

I am a beginner making use of a Roomdatabase. Mostly using it to load in and pass items between tables using simple relationships.
package com.example.allin
import android.app.AlertDialog
import android.net.Uri
import android.os.Bundle
import android.view.*
import android.widget.CheckBox
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.allin.model.Clothing
import com.example.allin.viewmodel.ClosetViewModel
import kotlinx.android.synthetic.main.fragment_clothing_tops_list.view.*
import kotlinx.android.synthetic.main.grid_clothing_item.view.*
class ClothingTopsList : Fragment() {
val args: ClothingTopsListArgs by navArgs()
/**
* Use this to get the query form Database of Tops
*/
private lateinit var mClosetViewModel: ClosetViewModel
private var adapter = ClothingTopsAdapter()
//This class should only display Clothing Tops in a RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_clothing_tops_list, container, false)
//instantiate the recyclerView
val recyclerView = view.clothing_top_rv
//asssign the adapter
recyclerView.adapter = adapter
recyclerView.layoutManager = GridLayoutManager(requireContext(), 2)
//Assign the correct data of Tops to the adapter of the RecyclerView
mClosetViewModel = ViewModelProvider(this).get(ClosetViewModel::class.java)
mClosetViewModel.selectAllTops().observe(viewLifecycleOwner, Observer { tops ->
adapter.setData(tops)
}
)
//If Item was selected. Call navController
setHasOptionsMenu(true)
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.add_outfits_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if(item.itemId == R.id.add_clothing_to_outfit_button){
val selectedDialog = AlertDialog.Builder(this.requireContext())
selectedDialog.setPositiveButton("Yes") { _, _ ->
**//Used Here**
val action = ClothingTopsListDirections.actionClothingTopsListToAddClothingToOutfits(args.currentOutfit,adapter.selectedItem,args.currentBottom,args.currentShoes, args.currentOuterWear)
findNavController().navigate(action)
}
selectedDialog.setNegativeButton("No") { _, _ -> }
**//Used Here**
val temp = adapter.selectedItem.type
selectedDialog.setTitle("Add $temp to the outfit?")
Toast.makeText(this.requireContext(), "Added to Outfit", Toast.LENGTH_SHORT).show()
selectedDialog.create().show()
}
return super.onOptionsItemSelected(item)
}
}
/**
* This page consists of all code for the RecyclerView of Clothing Tops for selection only to add to outfits.
*/
class ClothingTopsAdapter() : RecyclerView.Adapter<ClothingTopsAdapter.MyViewHolder>() {
private var clothingTopList = emptyList<Clothing>()
**//Created Here**
lateinit var selectedItem: Clothing
inner class MyViewHolder(item: View): RecyclerView.ViewHolder(item){
var checkBox: CheckBox = item.findViewById(R.id.clothing_cb)
}
//This inflates the EXACT SAME LAYOUT as ClothingList
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.grid_clothing_top_item, parent, false)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = clothingTopList[position]
holder.itemView.gl_clothing_type.text = currentItem.type
holder.itemView.gl_clothing_item_photo.setImageURI( Uri.parse(currentItem.image))
holder.itemView.grid_item.setOnClickListener {
if (!holder.itemView.clothing_cb.isChecked){
**//Used Here**
selectedItem = currentItem
holder.itemView.clothing_cb.isChecked = true
}else {
holder.itemView.clothing_cb.isChecked = false
}
}
}
override fun getItemCount(): Int {
return clothingTopList.size
}
fun setData(clothing: List<Clothing>) {
this.clothingTopList = clothing
notifyDataSetChanged()
}
}
For some reason it isn't properly adding the selected item from the recyclerView adapter to the selectedItem variable.
Would appreciate any insight into why this is happening all of a sudden.
It turned out that the error occurred because I was tapping the checkbox itself and not just the card.

Kotlin Recycleview ,how to make onItemClick for views

I am attempting to make a function :
In recyclerView
when you click the user image ,navigate to the userActivity,
and when you click the "gift" icon ,navigate to otherActivity .
As follow is my Adapter.kt :
package Users.UserReceiveGiftItem
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gearsrun.www.R
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.user_receive_gift_item.view.*
class UserReceiveGiftAdapter(val userList : List<UserReceiveGiftItem>) : RecyclerView.Adapter<UserReceiveGiftAdapter.UserHolder>(){
private lateinit var mListener :onItemClickListener
interface onItemClickListener{
fun onItemClick(view:View,position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return UserHolder(layoutInflater.inflate(R.layout.user_receive_gift_item,parent,false),mListener)
}
override fun onBindViewHolder(holder: UserReceiveGiftAdapter.UserHolder, position: Int) {
holder.render(userList[position])
}
override fun getItemCount(): Int = userList.size
class UserHolder(val view : View,listener:onItemClickListener) : RecyclerView.ViewHolder(view){
fun render(userList: UserReceiveGiftItem){
Picasso.get().load(userList.user_img).into(view.user_img)
view.user_name.text = userList.user_name
view.time.text = userList.time
view.userId.text = userList.userId
view.giftImg.setImageResource(userList.giftImg)
}
init {
view.setOnClickListener {
listener.onItemClick(it,absoluteAdapterPosition)
}
}
}
}
And I use in the Activity :
package com.gearsrun.www.UI.Receive
import Users.UserReceiveGiftItem.UserReceiveGiftAdapter
import Users.UserReceiveGiftItem.UserReceiveGiftItem
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.ActionBar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.gearsrun.www.R
import com.gearsrun.www.UI.Gift.AwaitingToUnwrapActivity
import com.gearsrun.www.UI.Sunflower.SunflowerAvailableActivity
import com.gearsrun.www.UI.User.UserDetailActivity
import kotlinx.android.synthetic.main.activity_who_receive_gift.*
import kotlinx.android.synthetic.main.user_receive_gift_item.view.*
class ReceiveActivity : AppCompatActivity() {
val userList : List<UserReceiveGiftItem> = listOf(
UserReceiveGiftItem(
"https://i.pinimg.com/564x/63/85/68/63856877880614e0dab080071513156f.jpg",
"Sharry",
"10 mins ago",
"517ddY",
R.drawable.donut
),
)
lateinit var gift_unwrap : ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_receive)
changeColor(R.color.font_green)
initRecycler()
gift_unwrap = findViewById(R.id.gift_unwrap)
gift_unwrap.setOnClickListener {
}
}
private fun changeColor(resourseColor: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = ContextCompat.getColor(applicationContext, resourseColor)
}
val bar: ActionBar? = supportActionBar
if (bar != null) {
bar.setBackgroundDrawable(ColorDrawable(resources.getColor(resourseColor)))
}
}
fun initRecycler(){
rvUser.layoutManager = LinearLayoutManager(this)
val adapter = UserReceiveGiftAdapter(userList)
rvUser.adapter = adapter
adapter.setOnItemClickListener(object:UserReceiveGiftAdapter.onItemClickListener{
override fun onItemClick(view: View, position: Int) {
view.user_img.setOnClickListener {
val intent = Intent(this#ReceiveActivity,UserDetailActivity::class.java)
startActivity(intent)
}
view.giftImg.setOnClickListener {
val intent =Intent(this#ReceiveActivity,SunflowerAvailableActivity::class.java)
startActivity(intent)
}
}
})
}
}
It is a bit rare that it is able to navigate successfully ,however ,it dosen't react for the first click ..Could you please take a look my code ?Thank you guys in advance !!
You are using the click listener of the list item to add more click listeners to its children. So the first time you click it, the children don't have listeners yet and won't do anything. It is also error prone that a parent and its children both have click listeners, because it's undefined what will happen if you change the click listener as you are clicking it.
Instead, you should define your interface in the adapter to handle both types of clicks. In the ViewHolder, set click listeners only on the relevant children to be clicked.
Also, I think you're misusing lateinit. I would make the property nullable. And it is redundant to have a setter function for a property's value.
If you make the ViewHolder class inner, then you don't have to pass instances of the listener to the view holder instances, which will cause problems if you ever change the Adapter's listener.
Finally, personally I think the listener should return the list item, not the specific view and position in the list. Those are implementation details that the outer class doesn't need to know about.
class UserReceiveGiftAdapter(val userList : List<UserReceiveGiftItem>) : RecyclerView.Adapter<UserReceiveGiftAdapter.UserHolder>(){
var onItemClickListener: OnItemClickListener? = null
interface OnItemClickListener{
fun onUserClick(item: UserReceiveGiftItem)
fun onGiftClick(item: UserReceiveGiftItem)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return UserHolder(layoutInflater.inflate(R.layout.user_receive_gift_item,parent,false))
}
override fun onBindViewHolder(holder: UserReceiveGiftAdapter.UserHolder, position: Int) {
holder.render(userList[position])
}
override fun getItemCount(): Int = userList.size
inner class UserHolder(val view : View) : RecyclerView.ViewHolder(view){
fun render(userList: UserReceiveGiftItem){
Picasso.get().load(userList.user_img).into(view.user_img)
view.user_name.text = userList.user_name
view.time.text = userList.time
view.userId.text = userList.userId
view.giftImg.setImageResource(userList.giftImg)
}
init {
view.user_img.setOnClickListener {
onItemClickListener?.onUserClick(userList[absoluteAdapterPosition])
}
view.giftImg.setOnClickListener {
onItemClickListener?.onGiftClick(userList[absoluteAdapterPosition])
}
}
}
}
In Activity or Fragment:
adapter.onItemClickListener = object: UserReceiveGiftAdapter.OnItemClickListener {
override fun onUserClick(item: UserReceiveGiftItem) {
val intent = Intent(this#ReceiveActivity, UserDetailActivity::class.java)
startActivity(intent)
}
override fun onGiftClick(item: UserReceiveGiftItem) {
val intent = Intent(this#ReceiveActivity, SunflowerAvailableActivity::class.java)
startActivity(intent)
}
}
What I think after looking your code is, that you are setting adapter to your recyclerview first and then you are injecting a click listener to adapter.
What happens here is that some items from recycler view are loaded and they wouldn't have the click listener at that time,
for example if you scroll down, the newly appeared items on the screen will have proper click listeners, now if you scroll up, the previous one's will also have listener attached.
What to do to get rid of this problem:
Change your initRecycler() method as below:
fun initRecycler(){
rvUser.layoutManager = LinearLayoutManager(this)
val adapter = UserReceiveGiftAdapter(userList)
adapter.setOnItemClickListener(object:UserReceiveGiftAdapter.onItemClickListener{
override fun onItemClick(view: View, position: Int) {
view.user_img.setOnClickListener {
val intent = Intent(this#ReceiveActivity,UserDetailActivity::class.java)
startActivity(intent)
}
view.giftImg.setOnClickListener {
val intent =Intent(this#ReceiveActivity,SunflowerAvailableActivity::class.java)
startActivity(intent)
}
}
})
//set adapter after setting click listener to your adapter.
rvUser.adapter = adapter
}

Kotlin - Set zoom level google map Android Studio

I've added the Google Map API and added my markers, but when the app is run, it is zoomed out quite a lot. I tried researching but I can't find what I need. Can someone please tell me how to set the zoom level once the map is loaded? E.g, it should show only a city not the whole world.
Here's my fragment code
package com.example.myassignment.Fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.myassignment.R
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import kotlinx.android.synthetic.main.fragment_destinations.*
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class DestinationsFragment : Fragment(), OnMapReadyCallback {
private var param1: String? = null
private var param2: String? = null
private lateinit var googleMap: GoogleMap
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
mapView.onCreate(savedInstanceState)
mapView.onResume()
mapView.getMapAsync(this)
}
override fun onMapReady(map: GoogleMap?) {
map?.let {
googleMap = it
}
val sydney = LatLng(-34.0, 151.0)
val stokeOnTrent = LatLng(53.025780,-2.177390)
val stokeCobrdige = LatLng(53.029380,-2.188740)
// Here's my markers
googleMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
googleMap.addMarker(MarkerOptions().position(stokeOnTrent).title("Stoke Center"))
googleMap.addMarker(MarkerOptions().position(stokeCobrdige).title("Cobridge - Stoke"))
googleMap.minZoomLevel
googleMap.moveCamera(CameraUpdateFactory.newLatLng(stokeOnTrent))
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_destinations, container, false)
}
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 DestinationsFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
DestinationsFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Thank you!
If you didn't resolve this already, you need to use the function newLatLngZoom() instead of newLatLng() and provide the zoom level - 10.
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(stokeOnTrent, someZoomLevel))
More info can be found here, but it says:
val homeLatLng = LatLng(latitude, longitude)
val zoomLevel = 15f
The zoom level controls how zoomed in you are on the map. The following list gives you an idea of what level of detail each level of zoom shows:
1: World
5: Landmass/continent
10: City
15: Streets
20: Buildings
Move the camera to homeLatLng by calling the moveCamera() function on the map object and pass in a CameraUpdate object using CameraUpdateFactory.newLatLngZoom(). Pass in the homeLatLng object and the zoomLevel.
map.moveCamera(CameraUpdateFactory.newLatLngZoom(homeLatLng, zoomLevel))

App crashes when navigating to a fragment that uses a RecyclerView and Room database with LiveData

I am currently building an app with a main activity which hosts a navHostFragment, and 3 fragments that are connected by a bottom navigation bar. My goal is to have each fragment use a Recycler View. I am using a Room database with an Adapter and LiveData for the "data". When I launch the app, I want to go to the Wallet fragment and see a vertical list of textViews. Because I am new and just starting out with this, I just wanted to have a very simple database of just text and then lay it out in a vertical format. Nothing too crazy yet. Any help would be greatly appreciated.
MainActivity
package com.example.android.pointmax
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import timber.log.Timber
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Plant tree to enable Debugging with Timber
Timber.plant(Timber.DebugTree())
// Find the bottomNavigation bar
val navView: BottomNavigationView = findViewById(R.id.nav_view)
// Find the fragment that will host the different fragments
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_wallet, R.id.navigation_recommended
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
WalletFragment
package com.example.android.pointmax.ui.wallet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.android.pointmax.CardAdapter
import com.example.android.pointmax.R
class WalletFragment : Fragment() {
private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var recyclerView: RecyclerView
private lateinit var viewModel: WalletViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_wallet, container, false)
recyclerView = rootView.findViewById(R.id.wallet_recyclerview)
viewModel = ViewModelProvider(
this
).get(WalletViewModel::class.java)
val linearLayoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewManager = linearLayoutManager
// Observe the ViewModel
viewModel.allCards.observe(viewLifecycleOwner, Observer { cards ->
viewAdapter = CardAdapter(cards)
})
return rootView
}
}
WalletViewModel
package com.example.android.pointmax.ui.wallet
import android.app.Application
import androidx.lifecycle.*
import com.example.android.pointmax.database.Card
import com.example.android.pointmax.database.CardRepository
import com.example.android.pointmax.database.CardRoomDatabase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class WalletViewModel(application: Application) : AndroidViewModel(application) {
private val repository: CardRepository
// Using LiveData and caching what getAlphabetizedWords returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
val allCards: LiveData<List<Card>>
init {
val cardsDao = CardRoomDatabase.getDatabase(application, viewModelScope).cardDao()
repository = CardRepository(cardsDao)
allCards = repository.allCards
}
/**
* Launching a new coroutine to insert the data in a non-blocking way
*/
fun insert(card: Card) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(card)
}
}
CardAdapter
package com.example.android.pointmax
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.android.pointmax.database.Card
class CardAdapter internal constructor(
private var cards: List<Card>
) : RecyclerView.Adapter<CardAdapter.CardViewHolder>() {
inner class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val cardItemView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item, parent, false)
return CardViewHolder(itemView)
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val current = cards[position]
holder.cardItemView.text = current.toString()
}
internal fun setWords(cards: List<Card>) {
this.cards = cards
notifyDataSetChanged()
}
override fun getItemCount() = cards.size
}
CardRepository
package com.example.android.pointmax.database
import androidx.lifecycle.LiveData
// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class CardRepository(private val cardDao: CardDao) {
// Room executes all queries on a separate thread.
// Observed LiveData will notify the observer when the data has changed.
val allCards: LiveData<List<Card>> = cardDao.getCards()
suspend fun insert(card: Card) {
cardDao.insert(card)
}
}
CardRoomDatabase
package com.example.android.pointmax.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
// Annotates class to be a Room Database with a table (entity) of the Word class
#Database(entities = arrayOf(Card::class), version = 1, exportSchema = false)
public abstract class CardRoomDatabase : RoomDatabase() {
abstract fun cardDao(): CardDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: CardRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): CardRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
CardRoomDatabase::class.java,
"card_database"
).addCallback(CardDatabaseCallback(scope)).build()
INSTANCE = instance
return instance
}
}
private class CardDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.cardDao())
}
}
}
suspend fun populateDatabase(cardDao: CardDao) {
// Delete all content here.
cardDao.deleteAll()
// Add sample words.
var card = Card("Petal Credit Card")
cardDao.insert(card)
card = Card("Discover IT")
cardDao.insert(card)
}
}
}
}
Card
package com.example.android.pointmax.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
#Entity(tableName = "card_table")
data class Card(
#PrimaryKey
#ColumnInfo(name = "cardName")
var card: String
)
CardDao
package com.example.android.pointmax.database
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
#Dao
interface CardDao {
#Query("SELECT * from card_table")
fun getCards(): LiveData<List<Card>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(card: Card)
#Query("DELETE FROM card_table")
suspend fun deleteAll()
}
I can launch the application but as soon as I go to the Wallet fragment, the application crashes with the following:
2020-04-23 19:24:18.676 5048-5048/com.example.android.pointmax E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android.pointmax, PID: 5048
java.lang.RuntimeException: Cannot create an instance of class com.example.android.pointmax.ui.wallet.WalletViewModel
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:275)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at com.example.android.pointmax.ui.wallet.WalletFragment.onCreateView(WalletFragment.kt:31)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:267)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106) 
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185) 
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150) 
at com.example.android.pointmax.ui.wallet.WalletFragment.onCreateView(WalletFragment.kt:31) 
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698) 
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320) 
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187) 
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224) 
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997) 
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953) 
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849) 
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413) 
at android.os.Handler.handleCallback(Handler.java:883) 
at android.os.Handler.dispatchMessage(Handler.java:100) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: java.lang.RuntimeException: cannot find implementation for com.example.android.pointmax.database.CardRoomDatabase. CardRoomDatabase_Impl does not exist
at androidx.room.Room.getGeneratedImplementation(Room.java:94)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:952)
at com.example.android.pointmax.database.CardRoomDatabase$Companion.getDatabase(CardRoomDatabase.kt:37)
at com.example.android.pointmax.ui.wallet.WalletViewModel.(WalletViewModel.kt:20)
The most essential part of your stack trace is here
`Caused by: java.lang.RuntimeException: cannot find implementation for com.example.android.pointmax.database.CardRoomDatabase. CardRoomDatabase_Impl does not exist at androidx.room.Room.getGeneratedImplementation(Room.java:94) at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:952) at com.example.android.pointmax.database.CardRoomDatabase$Companion.getDatabase(CardRoomDatabase.kt:37) at com.example.android.pointmax.ui.wallet.WalletViewModel.(WalletViewModel.kt:20)`
So the problem is Room couldn't generate class CardRoomDatabase_Impl (implementation of your abstract class CardRoomDatabase). Since you use Room annotation correctly, the only reason of your problem I could guess - you haven't included annotation processor in your build.gradle (app level). Check if it is in dependencies-section:
kapt "androidx.room:room-compiler:2.2.5"