Update ViewModel and View fails - kotlin

I'm trying to use Fragment and ViewModel to update a value from my view using ViewModel.
The problem is that is not updating the value that I want to send via viewModel (now it's hardcoded but I'm thinking in an API request to know a number to set it into my viewModel using postValue and this is not working as I expected)
Here's my code:
Fragment
private var numberedValue = 0.0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupObservers()
setupView()
}
private fun setupObservers(){
viewModel.obtainNumberedValue()
viewModel.getNumberedValue().observe(viewLifecycleOwner, { it ->
numberedValue = it
})
}
private fun setupView(){
binding.tvValue.text = numberedValue.toString()
}
ViewModel
private val numberedValue = MutableLiveData<Double>().apply { value = 0.0 }
fun obtainNumberedValue() {
numberedValue.postValue(200.00)
}
fun getNumberedValued(): LiveData<Double> = numberedValue
Ps. I got 0.0 instead 200.0

I think that you can improve your code if you do something like this
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupObservers()
}
private fun setupObservers(){
viewModel.obtainNumberedValue()
viewModel.getNumberedValue().observe(viewLifecycleOwner, { it ->
setupTextView(it)
})
}
private fun setupTextView(value: Double) {
binding.tvValue.text = "$value"
}
We remove this line private var numberedValue = 0.0
and we add the method inside to the observer method and in in we get any update in the value the ui will be updated

Related

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"

How to keep user loged in using SharedPreferences in Kotlin?

i have a problem here, i want to make login and register using SharedPreferences. The pass and getting data is already done and no error, but idk how to keep user loged in and everytime user close the app it will automatically go to my HomeFragment. And user can go to login again if they click logout button in my HomeFragment
this is my LoginFragment
override fun onCreateView(
...
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnSignIn.setOnClickListener {
getPrefData()
}
binding.tvRegister.setOnClickListener{
navControllerRegister()
}
}
private fun getPrefData(){
...
}
private fun navControllerSignIn(){
...
}
private fun navControllerRegister(){
...
}
this is my HomeFragment
override fun onCreateView(
...
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val pref = requireActivity().getSharedPreferences(Data.Preferences.PREF_ID, Context.MODE_PRIVATE)
binding.btnLogOut.setOnClickListener {
val editor = pref.edit()
editor.remove(Data.Preferences.IS_LOGIN)
editor.clear()
val intent = Intent(requireActivity(), LoginFragment::class.java)
startActivity(intent)
}
}
and this is my constant data
object Data{
object Preferences{
const val PREF_ID = "CHALLENGE_CHAPTER_4"
const val IS_LOGIN = "IS_LOGIN"
const val PREF_NAME = "NAME"
const val PREF_EMAIL = "EMAIL"
const val PREF_PASS = "PASS"
const val PREF_CONFIRM_PASS = "CONFIRM_PASS"
}
}

My RecyclerView in ViewModel crashing app - kotlin android studio

I have problem with RecyclerView into ViewModel.
RecyclerView without viewmodel working perfect.
My MainFragment:
private lateinit var shoppingListViewModel: ShoppingListViewModel
private lateinit var categoryAdapter: CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shoppingListViewModel = ViewModelProvider(requireActivity())[ShoppingListViewModel::class.java]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.categoryRV.layoutManager = LinearLayoutManager(requireContext())
shoppingListViewModel.allCategories.observe(viewLifecycleOwner, { <-- when I add this line,
updateCategories(it) crashing my app
})
}
private fun updateCategories(list: List<Category>) {
categoryAdapter = CategoryAdapter(list)
binding.categoryRV.adapter = categoryAdapter
}
My ViewModel:
class ShoppingListViewModel(application: Application) : AndroidViewModel(application) {
private val repository = Repository(application)
val allCategories = repository.showAllCategories()
}
My Repo:
class Repository(app: Application) {
private val shoppingListsDao = ShoppingListDatabaseBuilder.getInstance(app.applicationContext).shoppingListDao()
fun showAllCategories(): LiveData<List<Category>> {
return shoppingListsDao.showAllCategories()
}
}
My Interface Dao:
#Dao
interface ShoppingListDao {
#Query("SELECT * FROM category")
fun showAllCategories(): LiveData<List<Category>>
}
Everything looks good, no errors. I don't know what going on :(
I find my problem.
I need to update of version DB :)

RecyclerView in Fragment not populating

So I've been wrestling with this for days and I need some help. I've made this code work in an activity, but then I move it to a fragment it doesn't work. Everything else is the same between the two.
Using the debugger with the working Activity, the line
apiService = retrofit.create<HomeJsonApiService>(HomeJsonApiService::class.java)
goes to getItemCount(). However in the fragment it goes directly to onCreateView in the Fragment. I've attached my code below. Thanks in advance for the help! And be gentle. I'm still new to this :)
First is my fragment:
class TabHomeActivity : Fragment() {
val itemList = ArrayList<HomeCards>()
lateinit var adapter: HomeCardsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
adapter = HomeCardsAdapter()
var rv = binding.rvHomeCards
rv.adapter = adapter
loadData()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.cards_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun loadData() {
ApiManager.getInstance().service.listHeroes()
.enqueue(object : Callback<ResponseData<List<HomeCards>>> {
override fun onResponse(
call: Call<ResponseData<List<HomeCards>>>,
response: Response<ResponseData<List<HomeCards>>>
) {
val listData: List<HomeCards> = response.body()!!.data
// updating data from network to adapter
itemList.clear()
itemList.addAll(listData)
adapter.updateData(itemList)
adapter.notifyDataSetChanged()
}
override fun onFailure(call: Call<ResponseData<List<HomeCards>>>, t: Throwable) {
}
})
}
}
The HTTP request:
data class ResponseData<T> (
val code: Int,
val data: T
)
interface HomeJsonApiService {
#GET("marvel-heroes.asp?h=2")
fun listHeroes(): retrofit2.Call<ResponseData<List<HomeCards>>>
}
class ApiManager {
private var apiService: HomeJsonApiService? = null
init {
createService()
}
val service: HomeJsonApiService get() = apiService!!
private fun createService() {
val loggingInterceptor =
HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("Retrofit", message)
}
})
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)
.build()
val retrofit: Retrofit = Retrofit.Builder()
.client(client)
.baseUrl("https://www.mywebsite.com/jsonfolder/JSON/")
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(HomeJsonApiService::class.java)
}
companion object {
private var instance: ApiManager? = null
fun getInstance(): ApiManager {
return instance ?: synchronized(this) {
ApiManager().also { instance = it }
}
}
}
}
And my adapter:
class HomeCardsAdapter() : RecyclerView.Adapter<HomeCardsAdapter.ViewHolder>() {
private lateinit var itemList: List<HomeCards>
lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
val view = LayoutInflater.from(context).inflate(R.layout.cards_home, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return if (::itemList.isInitialized) itemList.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind()
}
fun updateData(list: List<HomeCards>) {
itemList = list;
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//var binding = ActivityMainBinding(layoutInflater(inf))
fun bind() {
val item = itemList.get(adapterPosition)
ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.name
ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.superheroName
Glide.with(context)
.load(item.photo)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.into(ViewHolder(itemView).itemView.findViewById<ImageView>(R.id.cardHomeIcon))
}
}
}
class HomeCards {
#SerializedName("superhero_name")
var superheroName: String = ""
var name: String = ""
var photo: String = ""
}
The main problem is:
var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
That is inside on onCreate but onCreateView is returning another view inflater.inflate(R.layout.cards_home, container, false)
So you are applying the adapter to a recycler that is on the binding, but the view on the screen is inflated from the layout. Change it to this:
private lateinit var binding: FragmentTabHomeActivityBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
binding = FragmentTabHomeActivityBinding.inflate(layoutInflater, container, false)
return binding.root
}
And move the code from from onCreate to onViewCreated but make sure to use the lateinit binding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = HomeCardsAdapter()
var rv = binding.rvHomeCards
rv.adapter = adapter
loadData()
}
After that there is a problem in your adapter: private lateinit var itemList: List<HomeCards> very specifically List<HomeCards>. The method notifyDataSetChanged doesn't work by changing or updating the reference of the data structure but when the collection is modified. Change it to this:
private val list = mutableListOf<HomeCards>()
override fun getItemCount(): Int {
return list.size()
}
fun updateData(list: List<HomeCards>) {
this.itemList.clear()
this.itemList.addAll(list)
notifyDataSetChanged()
}
If onResponse() gets called and provides response, verify that code updating UI is running on main/ui thread. Common source of issue when working with network (other threads).
activity?.runOnUiThread {
itemList.clear()
itemList.addAll(listData)
adapter.updateData(itemList)
adapter.notifyDataSetChanged()
}