Sharing value between two recyclerviews in one activity for Android - android

How to share value between two RecyclerView? That are in one Activity? Below is my code with just one RecyclerView binding. That is OK and I understand how it works. What I want to add another RecyclerView in the same page. Both RecyclerViews are tables. When user clicked on item in first RecyclerView anything changed in second RecyclerView. Repository usually returns List of data from Room database.
What I appreciate would be some code example or maybe some tutorial with example.
This is MainActivity.kt
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val oneViewModel: OneViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMaindBinding.inflate(layoutInflater)
setContentView(binding.root)
val recyclerViewOneAdapter = RecyclerViewOneAdapter ()
binding.apply {
recyclerViewOneAdapter.apply {
adapter = Adapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
oneViewModel.getItems.observe(this#MainActivity){
adapter.submitList(it)
}
}
}
}
This is ViewModel example
#HiltViewModel
class OneViewModel #Inject constructor(
repository: SybaseRepository
): ViewModel(){
val getItems = repository.getAllItemsFromDatabase().asLiveData()
}
What I suggest is something like this:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val oneViewModel: OneViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMaindBinding.inflate(layoutInflater)
setContentView(binding.root)
val recyclerViewOneAdapter = RecyclerViewOneAdapter ()
val recyclerViewSecondAdapter = RecyclerViewSecondAdapter()
binding.apply {
recyclerViewOneAdapter.apply {
adapter = recyclerViewOneAdapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
oneViewModel.getFirstItems.observe(this#MainActivity){
adapter.submitList(it)
}
recyclerViewTwoAdapter.apply {
adapter = recyclerViewSecondAdapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
oneViewModel.getSecondItems.observe(this#MainActivity){
adapter.submitList(it)
}
}
}
}
This is ViewModel example
#HiltViewModel
class OneViewModel #Inject constructor(
repository: SybaseRepository
): ViewModel(){
val getFirstItems = repository.getFirsttemsFromDatabase().asLiveData()
val getSecondItems = repository.getSecondItemsFromDatabase().asLiveData()
}
Am I missing something? Or is it correct way?

Related

RecyclerView.Adapter object does not work properly after extending a class

I want this search class to search for users in a database, i put some functions i use most of the time into an abstract class called for instance "Abs" that extends "AppCompactActivity"
I changed nothing other than the extended class, it works perfectly with the old class, however when i change it to the updated one it gives an error
This is the updated class
class SearchActivity : AbsBottom(R.layout.activity_search, R.id.bottomNav, R.id.search_nav) {
private var user = mutableListOf(UserSearch())
private var userAdapter = UserViewHolder()
lateinit var binding: ActivitySearchBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySearchBinding.inflate(layoutInflater)
//setContentView(binding.root)
//layout
binding.recyclerProfile.setHasFixedSize(true)
val llm = LinearLayoutManager(this)
llm.orientation = LinearLayoutManager.VERTICAL
binding.recyclerProfile.layoutManager = llm
//region Adapter Setup
binding.recyclerProfile.adapter = userAdapter
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
//do stuff
}
override fun onQueryTextChange(newText: String?): Boolean {
//do other stuff
}
})
//endregion
}
}
This is the original
class SearchActivity : AppCompatActivity() {
private var user = mutableListOf(UserSearch())
private var userAdapter = UserViewHolder()
lateinit var binding: ActivitySearchBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySearchBinding.inflate(layoutInflater)
setContentView(binding.root)
bottomNavigation()
//layout
binding.recyclerProfile.setHasFixedSize(true)
val llm = LinearLayoutManager(this)
llm.orientation = LinearLayoutManager.VERTICAL
binding.recyclerProfile.layoutManager = llm
// userAdapter.setUsers(user)
//region Adapter Setup
binding.recyclerProfile.adapter = userAdapter
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
//do stuff
}
override fun onQueryTextChange(newText: String?): Boolean {
//do other stuff
}
})
//endregion
}
}
This is what the abstract class looks like
abstract class AbsBottom(val idC: Int, val idB: Int, val ac: Int) : AppCompatActivity() {
protected lateinit var bottomNav : BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(idC)
bottomNav = findViewById(idB)
bottomNav.selectedItemId = ac
//etc
}
}
The error doesn't say much, it says that it can't see the userAdapter, and as a result it doesn't load the list of searched users
Fixed the issue, commenting the line
//setContentView(binding.root)
broke the binding.

How can I send a variable from a fragment to a view model in MVVM architecture in kotlin?

Well I am a beginner with android and kotlin so I have been trying to send a variable semesterSelected from the fragment ViewCourses to my viewmodel UserViewModel is the codes are down below.
`class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
// The variable I am trying to send to UserViewModel is -->> semesterSelected
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
The reason I am doing this is I am wanting a to send a variable to my repository UserRepository all the way from ViewCourses and thought sending this via UserViewModel might be a way .
class UserRepository(semesterSelected: String) {
// The variable I am expecting to get from UserViewModel
private var semesterSelected = semesterSelected
private val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("course-list/$semesterSelected")
#Volatile
private var INSTANCE: UserRepository? = null
fun getInstance(): UserRepository {
return INSTANCE ?: synchronized(this) {
val instance = UserRepository(semesterSelected)
INSTANCE = instance
instance
}
}
fun loadUsers(userList: MutableLiveData<List<CourseData>>) {
databaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val courseList: List<CourseData> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(CourseData::class.java)!!
}
userList.postValue(courseList)
} catch (e: Exception) {
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
I tried something like below
class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
**// Sending the variable as parameter**
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel(semesterSelected: String) : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
but doing this my app crashes . how can this be done ?
Thanks in Advance.
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
UserViewModel(semesterSelected)::class.java NOR UserViewModel::class.java is a constructor for the view model.
If you would want to have ViewModel with that NEEDS initial parameters, you will have to create your own factory for that - which is a tad more complicated and for your case, it might be overkill for what you are trying to do but in the longterm it will pay off(Getting started with VM factories).
With that said, your needs can be easily solved by one function to initialize the view model.
class UserViewModel() : ViewModel() {
private lateinit var repository: UserRepository
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
fun initialize(semesterSelected: String) {
repository = UserRepository("CSE/year3semester1").getInstance()
repository.loadUsers(_allUsers)
}
}
A ViewModel must be created using a ViewModelProvider.Factory. But there is a default Factory that is automatically used if you don't specify one. The default factory can create ViewModels who have constructor signatures that are one of the following:
empty, for example MyViewModel: ViewModel.
saved state handle, for example MyViewModel(private val savedStateHandle: SavedStateHandle): ViewModel
application, for example MyViewModel(application: Application): AndroidViewModel(application)
both, for example MyViewModel(application: Application, private val savedStateHandle: SavedStateHandle): AndroidViewModel(application)
If your constructor doesn't match one of these four above, you must create a ViewModelProvider.Factory that can instantiate your ViewModel class and use that when specifying your ViewModelProvider. In Kotlin, you can use by viewModels() for easier syntax. All the instructions for how to create your ViewModelFactory are here.

In Kotlin Android, viewModel has no observer

I made a toolbar in a BaseActivity to implement a common and the code is as follows.
// BaseActivity
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
lateinit var cartCnt: TextView
private val viewModel by lazy {
ViewModelProvider(this, CartViewModelFactory())[CartViewModel::class.java]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, layoutId)
mContext = this
viewModel.cartItemList.observe(this){
cartCnt.text = it.size.toString()
}
supportActionBar?.let {
setCustomActionBar()
}
}
open fun setCustomActionBar() {
val defActionBar = supportActionBar!!
defActionBar.elevation = 0F
defActionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
defActionBar.setCustomView(R.layout.custom_action_bar)
val toolbar = defActionBar.customView.parent as Toolbar
toolbar.setContentInsetsAbsolute(0, 0)
cartCnt = defActionBar.customView.findViewById(R.id.cartCnt)
}
}
In BaseActivity, the text of TextView called cartCnt (the number of products currently in the shopping cart) is observed from MutableLiveData in the CartView Model.
Is as follows : cartviewmodel
// CartViewModel
class CartViewModel() : ViewModel() {
private val list = mutableListOf<Cart>()
private val _cartItemList: MutableLiveData<List<Cart>> = MutableLiveData()
val cartItemList: LiveData<List<Cart>> get() = _cartItemList
private val repository by lazy {
CartRepository.getInstance()
}
init {
getAllCartItems()
}
fun getAllCartItems() {
viewModelScope.launch {
repository!!.getRequestMyCartList {
if (it is Result.Success) {
list.addAll(it.data.data!!.carts)
_cartItemList.value = list
}
}
}
}
fun addToCartItem(id: Int) {
viewModelScope.launch {
repository!!.postRequestAddCart(id) {
if (it is Result.Success) {
list.add(it.data.data!!.cart)
_cartItemList.value = list
}
}
}
}
}
The observer of the View Model existed only in SplashActivity, which first inherited BaseActivity. (verified as a function hasObservers.).
When I clicked on the shopping basket button on the product list page, I communicated with the server and confirmed that the shopping basket data was normally put in the server table, and I also confirmed that the 200 status code was returned normally.
However, when Fragment, which has a product list page, declared cartViewModel and called the addToCartItem function, there was no observer attached to the cartViewModel. This is the part confirmed through the hasObservers function.
The view structure roughly has MainActivity inherited from BaseActivity, and TodayFragment exists in MainActivity.
And, TodayFragment's code is as follows.
// TodayFragment
class TodayFragment : BaseFragment<FragmentTodayBinding>() {
override val layoutId: Int = R.layout.fragment_today
private lateinit var bannerViewPager: BannerRecyclerviewAdapter
private lateinit var productAdapter: ProductHorizonRecyclerviewAdapter
private val cartViewModel by lazy {
ViewModelProvider(this, CartViewModelFactory())[CartViewModel::class.java]
}
override fun init() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initProductRecyclerview()
setValues()
}
override fun setValues() {
HomeViewModel.currentPosition.observe(viewLifecycleOwner) {
binding.bannerViewpager.currentItem = it
}
}
private fun initProductRecyclerview(){
binding.productRecyclerView.apply {
productAdapter = ProductHorizonRecyclerviewAdapter(){
cartViewModel.addToCartItem(it.id)
}
adapter = productAdapter
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
}
}
}
In other words, when the cartViewModel's addToCartItem function is called through the product list page in TodayFragment, the mutableLiveData of the cartViewModel changes, and the cartCnt TextView of BaseActivity is observing this change.
In this situation, I wonder why the first SplashActivity, which appears in the activity stack structure, has observer, and then disappears in the Today Fragment.
Somebody help me.
You are recreating cartViewModel in TodayFragment by passing it a factory which is why it doesn't have the BaseActivity observer. Try this from within TodayFragment
private val cartViewModel: CartViewModel by activityViewModels()
or
private val cartViewModel by lazy {
ViewModelProvider(requireActivity())[CartViewModel::class.java]
}
Then if you call cartViewModel.addToCartItem() in TodayFragment it should call the observer in BaseActivity.

Filling in a MultiView ViewHolder inside a RecyclerView With Data Binding

So I was able to get to a close point in creating the MultiView ViewHolder, but I am still a bit confused with some details. First how would I fill in the RecyclerView since I have multiple data classes(in this case, manually). Second, how would the Adapter know when to show a particular view? I'll leave the code here
Data Class(es)
sealed class InfoRecyclerViewItems{
class WithPicture (
val id: Int,
val movieName: String,
val thoughts: String
): InfoRecyclerViewItems()
class WithoutPicture(
val id: Int,
val movieName: String,
val thoughts: String
): InfoRecyclerViewItems()
}
The Adapter
class RecyclerViewAdapter(infoItems: MutableList<InfoRecyclerViewItems>): RecyclerView.Adapter<MainViewHolder>() {
private var infoItems1: MutableList<InfoRecyclerViewItems>
init {
this.infoItems1 = infoItems
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return when(viewType){
R.layout.container_one -> MainViewHolder.WithPictureViewHolder(
ContainerOneBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
)
R.layout.container_two -> MainViewHolder.WithoutPictureViewHolder(
ContainerTwoBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
)
else -> throw IllegalArgumentException("Invalid view given")
}
}
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
when(holder){
is MainViewHolder.WithPictureViewHolder -> holder.bind(infoItems1[position] as InfoRecyclerViewItems.WithPicture)
is MainViewHolder.WithoutPictureViewHolder -> holder.bind(infoItems1[position] as InfoRecyclerViewItems.WithoutPicture)
}
}
override fun getItemCount() = infoItems1.size
override fun getItemViewType(position: Int): Int {
return when(infoItems1[position]){
is InfoRecyclerViewItems.WithPicture -> R.layout.container_one
is InfoRecyclerViewItems.WithoutPicture -> R.layout.container_two
}
}
}
The ViewHolder(s)
sealed class MainViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
class WithPictureViewHolder(private val binding: ContainerOneBinding) : MainViewHolder(binding){
fun bind(items: InfoRecyclerViewItems.WithPicture){
binding.part1 = items
binding.executePendingBindings()
}
}
class WithoutPictureViewHolder(private val binding: ContainerTwoBinding) : MainViewHolder(binding){
fun bind(items: InfoRecyclerViewItems.WithoutPicture){
binding.part2 = items
binding.executePendingBindings()
}
}
}
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this#MainActivity)
}
}
}
Any suggestions are welcomed, Thank You.
SO I figured it out. based on this setup, I can create an empty MutableList and then call each different data class that I want to fill in. those data classes are linked to the ViewHolder that it is associated to, thus creating two different views inside the RecylerView.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var manager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val information: MutableList<InfoRecyclerViewItems> = ArrayList()
information.add(InfoRecyclerViewItems.WithPicture(2, "The Fallen", "ok"))
information.add(InfoRecyclerViewItems.WithoutPicture(4, "Black Panther", "10/10, Fantastic Movie"))
manager = LinearLayoutManager(this)
binding.recyclerView.apply {
adapter = RecyclerViewAdapter(information)
layoutManager = manager
}
}
}
Then you can just keep adding to whatever view you chose to add it to
P.S. The other files(ViewHolder, Adapter and Data Class) stay the same.

State Management with ViewModel on Android when on a flow

Basically I have a state management system using ViewModel that looks like this:
class ViewModelA: ViewModel() {
private val repository: RepositoryA by inject()
private val _stateLiveData = MutableLiveData<ViewState>()
val stateLiveData: LiveData<ViewState> get() = _stateLiveData
private val _eventLiveData = SingleLiveEvent<ViewEvent>()
val eventLiveData: LiveData<ViewEvent> get() = _eventLiveData
private val exceptionHandler = CoroutineExceptionHandler { _, _ ->
_stateLiveData.postValue(ViewState.Error)
}
fun loadList() {
if (_stateLiveData.value is ViewState.Loading) return
launch(exceptionHandler) {
_stateLiveData.run {
value = ViewState.Loading
value = repository.getDocumentList().let {
if (it.isEmpty()) ViewState.Error
else ViewState.Data(it)
}
}
}
}
}
But whenever I am sharing a ViewModel with several Fragments, it becomes bigger and bigger. I am looking for a solution for this, because I don't want to centralize all the logic for an entire application flow inside a ViewModel and I also don't want to pass arguments here and there all the time.
PS: Sorry about my bad english.
Edit: Clarify a bit the question.
I didn't quite understand your question. However, if your question was as follows:
How can I share the same ViewModel Object and use it inside multiple Fragments.
You can check the documentation of ViewModelProvider which is a utility class that provides ViewModels for a specific scope like Activity.
Following is an example code of the usage of ViewModelProvider within two Fragments that will be created and used in the same Activity object:
// An example ViewModel
class SharedViewModel : ViewModel() {
val intLiveData = MutableLiveData<Int>() // an example LiveData field
}
// the first fragment
class Fragment1 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = requireActivity().let { activity ->
ViewModelProvider(activity).get(SharedViewModel::class.java)
}
}
}
// the other fragment
class Fragment2 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = requireActivity().let { activity ->
ViewModelProvider(activity).get(SharedViewModel::class.java)
}
}
}

Categories

Resources