LiveData doesn't triggered observing it from Fragment - android

I've a Fragment that observes a LiveData contained in a ViewModel to pass information in an RecyclerView.Adapter.
LiveData in the Fragment isn't triggered, but if I move it in an Activity, it's triggered.
Here's the code:
class ProfileViewModel : ViewModel() {
private val profileLiveData: MutableLiveData<User> = MutableLiveData(
private val followersPreview: LiveData<Query> =
Transformations.map(profileLiveData) { profile ->usersApi.getFollowers(profile.followers) }
private val followingPreview: LiveData<Query> =
Transformations.map(profileLiveData) { profile -> usersApi.getFollowing(profile.following) }
fun getProfile(userDocumentId: String): LiveData<User> {
usersApi.getProfile(userDocumentId)
.addSnapshotListener { querySnapshot, firebaseFirestoreException ->
if (firebaseFirestoreException != null) {
Log.d("Exception get profile", firebaseFirestoreException.message!!)
// Handle exception
}
val profileUser: User = querySnapshot?.toObject(User::class.java)!!
profileLiveData.value = profileUser
}
return profileLiveData
}
fun getProfileFollowers(): LiveData<Query> {
return followersPreview
}
}
class ListsProfileFragment : Fragment() {
private lateinit var fragmentListsBinding: FragmentListsProfileBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentListsBinding = FragmentListsProfileBinding.inflate(layoutInflater)
return fragmentListsBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val profileViewModel = ViewModelProviders.of(this).get(ProfileViewModel::class.java);
Log.d("Viewmodel", profileViewModel.toString())
profileViewModel
.getProfileFollowers()
.observe(viewLifecycleOwner,
Observer {
it.addSnapshotListener { querySnapshot, firebaseFirestoreException ->
val followers = mutableListOf<UserPreview>()
querySnapshot?.documents?.forEach { doc ->
val follower: UserPreview = UserPreview(
doc.get("username") as String,
doc.get("profileImage") as String
)
followers.add(follower)
}
fragmentListsBinding.recyclerViewFollowers.layoutManager =
LinearLayoutManager(this.activity, RecyclerView.HORIZONTAL, false)
fragmentListsBinding.recyclerViewFollowers.adapter =
AccountsAdapter(followers)
}
})
}
Could anyone help me? Thanks in advance.

Resolved using this code to load the ViewModel:
profileViewModel =
activity?.run { ViewModelProviders.of(this).get(ProfileViewModel::class.java) }
?: throw Exception("Invalid Activity")

Related

Problem with local database and remote database data when updating local data

I'm doing a practise with the rick and morty api and I have two fragments, one with a recycleview with the characters and another one with the detail of the character, where also you can update some of the values.
My problem is that when I update a value, if I go back to the main fragment with the recycle view, that value is updated but when I go back again to the detail, the value is again the original one. I don't know how to fix it.
This is my detail fragment:
class GetCharacterDetail: Fragment() {
private var binding: CharacterDetailFragmentBinding by autoCleared()
private val viewModel: CharacterDetailViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = CharacterDetailFragmentBinding.inflate(inflater, container, false)
val edit = binding.editButton
val save = binding.saveBotton
changeStateOnEdit(edit, save)
save.setOnClickListener {
val gender = binding.characterGenderText.text.toString()
val status = binding.characterStatusText.text.toString()
val species = binding.characterSpeciesText.text.toString()
updateCharacterDetails(gender, status, species, edit, save)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.getInt("id")?.let { viewModel.start(it) }
setupObservers()
}
private fun setupObservers() {
viewModel.character.observe(viewLifecycleOwner, Observer {
when (it.status) {
Status.StatusEnum.SUCCESS -> {
bindCharacter(it.data!! as CharacterEntity)
binding.progressBar.visibility = View.GONE
binding.characterDetailLayout.visibility = View.VISIBLE
}
Status.StatusEnum.ERROR ->
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
Status.StatusEnum.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.characterDetailLayout.visibility = View.GONE
}
}
})
}
private fun bindCharacter(character: CharacterEntity) {
if (character != null) {
binding.characterName.text = character.name
binding.characterSpeciesText.setText(character.species)
binding.characterStatusText.setText(character.status)
binding.characterGenderText.setText(character.gender)
Glide.with(binding.root)
.load(character.image)
.into(binding.characterImage)
}
}
private fun changeStateOnEdit(edit: ImageButton, save: MaterialButton) {
edit.setOnClickListener(View.OnClickListener {
edit.isVisible = false
binding.characterGender.isEnabled = true
binding.characterSpecies.isEnabled = true
binding.characterStatus.isEnabled = true
save.isVisible = true
})
}
private fun updateCharacterDetails(gender: String, status: String, species: String,edit: ImageButton, save: MaterialButton) {
viewModel.updateCharacterDetails(gender, status, species)
viewModel.character.observe(viewLifecycleOwner, Observer {
when (it.status) {
Status.StatusEnum.SUCCESS -> {
Toast.makeText(activity, "Personaje actualizado correctamente", Toast.LENGTH_SHORT).show()
edit.isVisible = true
binding.characterGender.isEnabled = false
binding.characterSpecies.isEnabled = false
binding.characterStatus.isEnabled = false
save.isVisible = false
bindCharacter(it.data!!)
}
Status.StatusEnum.ERROR ->
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
Status.StatusEnum.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.characterDetailLayout.visibility = View.GONE
}
}
})
}
}
And this is my ViewModel:
class CharacterDetailViewModel #Inject constructor(
private val repository: CharacterRepository
) : ViewModel() {
private val idCharacter = MutableLiveData<Int>()
val character = idCharacter.switchMap { id ->
repository.getCharacter(id)
}
fun updateCharacterDetails(gender: String, status: String, species: String) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val id = idCharacter.value ?: return#withContext
repository.updateCharacterDetail(id, gender, status, species)
}
}
}
fun start(id: Int) {
idCharacter.value = id
}
}
Herew is the repository:
class CharacterRepository #Inject constructor(
private val api : CharacterService,
private val characterDao: CharacterDao
) {
fun getAllCharacters() = getEntitiesOperation(
databaseQuery = { characterDao.getAllCharacters() },
networkCall = { api.getCharacters() },
saveCallResult = { characterDao.insertAll(it.results) }
)
fun getCharacter(id: Int) = getEntitiesOperation(
databaseQuery = { characterDao.getCharacter(id) },
networkCall = { api.getCharacter(id) },
saveCallResult = { characterDao.insert(it) }
)
fun deleteCharacter(id: Int) = characterDao.deleteCharacter(id)
fun updateCharacterDetail(id: Int, gender:String, status:String, species:String) =
characterDao.updateCharacterDetail(id, gender, status, species)
}
And the function I use to take the data from local database if there is data in it. Here is where I think it is the problem since I think that something has to be recovered wrong and that localData is null and then the method look for the data on the api
fun <T, A> getEntitiesOperation(databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Status<A>,
saveCallResult: suspend (A) -> Unit):
LiveData<Status<T>> = liveData(Dispatchers.IO) {
emit(Status.loading())
val source = databaseQuery.invoke().map { Status.success(it) }
emitSource(source)
val localData = source.value?.data
if (localData != null) return#liveData
val responseStatus = networkCall.invoke()
if (responseStatus.status == StatusEnum.SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == StatusEnum.ERROR) {
emit(Status.error(responseStatus.message!!))
emitSource(source)
}
}
I've been with this problem all day and I don't know what to do or how to fix it. Thank you in advance for the help

RecyclerView doesn't appear in a Fragment

Why doesn't RecyclerView appear in my fragment? I've added recyclerview adapter in the fragment, but it still didn't appear. Here are the codes:
FollowersFragment.kt
class FollowersFragment : Fragment() {
private lateinit var binding: FragmentFollowersBinding
companion object {
private const val TAG = "FollowersFragment"
const val ARG_NAME = "userName"
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_followers, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentFollowersBinding.inflate(layoutInflater)
val username = arguments?.getString(ARG_NAME)
val layoutManager = LinearLayoutManager(requireActivity())
binding.rvFollowers.layoutManager = layoutManager
val itemDecoration = DividerItemDecoration(requireActivity(), layoutManager.orientation)
binding.rvFollowers.addItemDecoration(itemDecoration)
val client = ApiConfig.getApiService().getFollowers(username.toString(),"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")
client.enqueue(object : Callback<List<FollowsResponseItem>> {
override fun onResponse(
call: Call<List<FollowsResponseItem>>,
response: Response<List<FollowsResponseItem>>
) {
if(response.isSuccessful){
val responseBody = response.body()
if(responseBody!=null){
Log.d(TAG,responseBody.toString())
setUserData(responseBody)
}else{
Log.e(TAG,"onFailure: ${response.message()}")
}
}
}
override fun onFailure(call: Call<List<FollowsResponseItem>>, t: Throwable) {
Log.e(TAG, "onFailure: ${t.message}")
}
})
}
fun setUserData(item: List<FollowsResponseItem>){
val listUser = ArrayList<UserResponse>()
val executor = Executors.newSingleThreadExecutor()
executor.execute {
try {
for (i in 0..item.size-1) {
if(item.size>5 && i>5){
break
}
val client = ApiConfig.getApiService()
.getUser(item.get(i).login, "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")
client.enqueue(object : Callback<UserResponse> {
override fun onResponse(
call: Call<UserResponse>,
response: Response<UserResponse>
) {
if (response.isSuccessful) {
val responseBody = response.body()
if (responseBody != null) {
listUser.add(responseBody)
if(i==4 || item.get(i).login.equals(item.get(item.size-1).login)){
Log.d(TAG,"user : $listUser")
val adapter = ListUserAdapter(listUser)
binding.rvFollowers.adapter = adapter
Log.d(TAG,adapter.toString())
adapter.setOnItemClickCallback(object: ListUserAdapter.OnItemClickCallback{
override fun onItemClicked(data: UserParcelable) {
showSelectedUser(data)
}
})
}
} else {
Log.e(TAG, "onFailure: ${response.message()}")
}
}
}
override fun onFailure(call: Call<UserResponse>, t: Throwable) {
Log.e(TAG, "onFailure: ${t.message}")
}
})
}
} catch(e: InterruptedException) {
e.printStackTrace()
}
}
}
private fun showSelectedUser(data: UserParcelable) {
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailBinding
private var getUserName: String ="sidiqpermana"
companion object{
const val EXTRA_DATA = "extra_data"
#StringRes
private val TAB_TITLES = intArrayOf(
R.string.tab_text_1,
R.string.tab_text_2
)
}
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
binding = ActivityDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
val data = intent.getParcelableExtra<UserParcelable>(EXTRA_DATA) as UserParcelable
val sectionsPagerAdapter = SectionsPagerAdapter(this)
val viewPager: ViewPager2 = binding.viewPager
viewPager.adapter = sectionsPagerAdapter
val tabs: TabLayout = binding.tabs
sectionsPagerAdapter.userName = data.login
TabLayoutMediator(tabs,viewPager){ tab, position ->
tab.text = resources.getString(TAB_TITLES[position])
}.attach()
getUserName = data.login
showInfo(data)
}
fun getUserName() : String{
return getUserName
}
#SuppressLint("SetTextI18n")
private fun showInfo(data: UserParcelable){
Glide.with(this#DetailActivity)
.load(data.avatar_url)
.into(binding.detailPp)
if(data.name.equals("null")) binding.detailName.setText("No Name") else binding.detailName.setText(data.name)
binding.detailUsername.setText(data.login)
if(data.bio.equals("null")) binding.detailBio.setText("No Name") else binding.detailBio.setText(data.bio)
binding.detailFollowers.setText("${data.followers} Followers")
binding.detailFollowings.setText("${data.following} Following")
if(data.location.equals("null")) binding.detailLocation.setText("No Location") else binding.detailLocation.setText(data.location)
}
}
ListUserAdapter.kt (RecyclerView Adapter)
class ListUserAdapter (private val listUser: ArrayList<UserResponse>) : RecyclerView.Adapter<ListUserAdapter.ListViewHolder>() {
private lateinit var onItemClickCallback: OnItemClickCallback
fun setOnItemClickCallback(onItemClickCallback: OnItemClickCallback){
this.onItemClickCallback = onItemClickCallback
}
class ListViewHolder(var binding: ItemUsersRowBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ListViewHolder {
val binding = ItemUsersRowBinding.inflate(LayoutInflater.from(viewGroup.context),viewGroup,false)
return ListViewHolder(binding)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
var (followers,avatar_url,following, name,bio, location, login) = listUser[position]
name = name ?: "No Name"
bio = bio ?: "No Bio"
location = location?: "No Location"
holder.apply {
Glide.with(itemView.getContext())
.load(avatar_url)
.into(binding.photoProfile)
binding.profileName.setText(name.toString())
binding.username.setText(login)
binding.followers.setText("$following Followers")
binding.followings.setText("$following Followings")
binding.location.setText(location.toString())
val detailUser = UserParcelable(followers,avatar_url,following,
name.toString(), bio.toString(), location.toString(), login)
itemView.setOnClickListener{ onItemClickCallback.onItemClicked(detailUser)}
}
}
override fun getItemCount(): Int {
return listUser.size
}
interface OnItemClickCallback {
fun onItemClicked(data: UserParcelable)
}
}
Help me solve this problem please.
There is no need to initialize adapter every time you want to update the list. Either make your ListUserAdapter extend ListAdapter and than use adapter.submitList(listUser) or if you want to extend RecyclerView.Adapter as you do, you can do the following :
class ListUserAdapter () : RecyclerView.Adapter<ListUserAdapter.ListViewHolder>() {
private val listUser: List<UserResponse>
fun submitList(newList: List<UserResponse>) {
listUser = newList
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val listItem = listUser[position]
...
}
override fun getItemCount(): Int {
return listUser.size
}
}
I suggest you go with ListAdapter. Check if Log.d(TAG,"user : $listUser") is printed, if it is and listUser is not empty than call adapter.submitList(listUser) and RV should be populated.
You have missed to notify adapter about the changes, So after
binding.rvFollowers.adapter = adapter call adapter.notifyDataSetChanged()

firebase realtime database: messenger

I am building a chatting app using firebase realtime database.
I am storing the messages in both accounts simultaneously.
This is my fragment to send message:
class DmFragment : Fragment() {
lateinit var binding: FragmentDMBinding
lateinit var user: User
val list = ArrayList<Message>()
lateinit var auth: FirebaseAuth
lateinit var firebaseDatabase: FirebaseDatabase
lateinit var reference: DatabaseReference
lateinit var usersDMAdapter: UsersDMAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
user = it.getSerializable("user") as User
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDMBinding.inflate(LayoutInflater.from(requireContext()), container, false)
auth = FirebaseAuth.getInstance()
val currentUser = auth.currentUser!!
firebaseDatabase = FirebaseDatabase.getInstance()
reference = firebaseDatabase.getReference("users")
binding.apply {
sendBtn.setOnClickListener {
val smsText = message.text.toString()
val simpleDateFormat = SimpleDateFormat("dd.MM.yyyy HH.ss")
val date = simpleDateFormat.format(Date())
val message1 = Message(smsText, date, currentUser.uid, user.uid)
val key = reference.push().key
reference.child("${currentUser.uid}/messages/${user.uid!!}/$key")
.setValue(message1)
reference.child("${user.uid}/messages/${currentUser.uid}/$key")
.setValue(message1)
message.text.clear()
}
reference.child("${currentUser.uid}/messages/${user.uid}")
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val children = snapshot.children
for (child in children) {
val value = child.getValue(Message::class.java)
if (value != null) {
list.add(value)
}
}
usersDMAdapter = UsersDMAdapter(list, currentUser.uid)
messageRv.adapter = usersDMAdapter
}
override fun onCancelled(error: DatabaseError) {
}
})
back.setOnClickListener {
findNavController().popBackStack()
}
userName.text = user.displayName
Picasso.get().load(user.photoUrl).into(userImage)
}
return binding.root
}
}
This is the adapter file that i used in the DMFragment:
class UsersDMAdapter(var list: List<Message>, var uid: String) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
inner class FromVh(val fromBinding: ItemFromBinding) :
RecyclerView.ViewHolder(fromBinding.root) {
fun onBind(message: Message) {
fromBinding.apply {
messageTv.text = message.message
messageDateTv.text = message.date
}
}
}
inner class ToVh(val toBinding: ItemToBinding) :
RecyclerView.ViewHolder(toBinding.root) {
fun onBind(message: Message) {
toBinding.apply {
messageTv.text = message.message
messageDateTv.text = message.date
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == 1) {
return FromVh(
ItemFromBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else {
return ToVh(
ItemToBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (getItemViewType(position)==1){
val fromVh = holder as FromVh
fromVh.onBind(list[position])
}else{
val toVh = holder as ToVh
toVh.onBind(list[position])
}
}
override fun getItemViewType(position: Int): Int {
return if (list[position].fromUid == uid) {
1
} else{
2
}
}
override fun getItemCount(): Int = list.size
}
This is my fragment that shows all users excluding myself:
class UserRecyclerFragment : Fragment() {
lateinit var binding: FragmentUserRecyclerBinding
lateinit var userRvAdapter: UserRvAdapter
var list = ArrayList<User>()
lateinit var auth: FirebaseAuth
lateinit var firebaseDatabase: FirebaseDatabase
lateinit var reference: DatabaseReference
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentUserRecyclerBinding.inflate(
LayoutInflater.from(requireContext()),
container,
false
)
auth = FirebaseAuth.getInstance()
val currentUser = auth.currentUser!!
firebaseDatabase = FirebaseDatabase.getInstance()
reference = firebaseDatabase.getReference("users")
val email = currentUser.email
val displayName = currentUser.displayName
val photo = currentUser.photoUrl
val phoneNumber = currentUser.phoneNumber
val uid = currentUser.uid
val user = User(
email,
displayName,
photo.toString(),
phoneNumber,
uid
)
reference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
list.clear()
val filterList = arrayListOf<User>()
val children = snapshot.children
for (child in children) {
val value = child.getValue(User::class.java)
if (value != null && uid != value.uid) {
list.add(value)
}
if (value != null && value.uid == uid) {
filterList.add(value)
}
}
if (filterList.isEmpty()) {
reference.child(uid).setValue(user)
}
userRvAdapter = UserRvAdapter(list, object : UserRvAdapter.OnItemClickListener {
override fun onItemCick(user: User) {
val bundle = Bundle()
bundle.putSerializable("user", user)
findNavController().navigate(R.id.dmFragment, bundle)
}
})
binding.rv.adapter = userRvAdapter
}
override fun onCancelled(error: DatabaseError) {
}
})
return binding.root
}
}
Whenever i chat (send/receive) in DM, it is showing messages. But when I go back, messages that added in my user account is being deleted automatically. If i come back again, it is not showing the messages that have been saved in other person's account either. And i do not know what to do. Can anyone help?

Clicking on Recyclerview item has a late item

I have an application using databinding, livedata, room, kotlin koroutines, viewmodel, retrofit and koin. I have one activity, and two fragments.
UserListFragment: Show in a recyclerview a list of user items.
UserFullProfileFragment: Show the user item detail.
When the application is running, an external API is called to retrieve a list of users and display it in a recyclerview. Then, if I click on one item, an external API is called to get the detail of the current user whith its ID.
The problem is when I click on one item at the first, everything is going well but for following items, this is the detail of previous item which is displayed and so on and so forth.
Any ideas ?
UserRepository:
class UserRepositoryImpl (private val userApi: UserApi, private val userDao: UserDao, private val networkStateManager: NetworkStateManager) : UserRepository {
override suspend fun getUserList(): Result<List<UserListItem>> {
if (networkStateManager.hasNetWorkConnection()) {
return try {
// get user list from user API
val response = userApi.getUserList()
if (response.isSuccessful) {
Log.d("REPO", "get users from api")
response.body()?.let { userResponse ->
Log.d("REPO", "response:$response")
val userList = userResponse.data
// convert user API object to user entity
val entities = userList.map { it.toUserEntity() }
// save user list in database
withContext(Dispatchers.IO) { userDao.addUsers(entities) }
// convert user entity to user model
val userItemList = entities.map { it.toUserListItem() }
return Result.Success(userItemList)
} ?: handleFailure(response)
} else {
handleFailure(response)
}
} catch (e: Exception) {
return Result.Failure(e, e.localizedMessage)
}
} else {
// get user list from database if no network
val data = withContext(Dispatchers.IO) { userDao.findAllUsers() }
return if (data.isNotEmpty()) {
Log.d("REPO", "get users from db")
val userItemList = data.map { it.toUserListItem() }
Result.Success(userItemList)
} else {
Result.Failure(Exception("error"), "no network connection")
}
}
}
override suspend fun getUserFullProfile(userId: String): Result<UserFullProfile> {
if (networkStateManager.hasNetWorkConnection()) {
return try {
// get user from user API
val response = userApi.getUserFullProfile(userId)
if (response.isSuccessful) {
Log.d("REPO", "get users from api")
response.body()?.let { userResponse ->
Log.d("REPO", "response:$userResponse")
// convert user API object to user entity
val userEntity = userResponse.toUserEntity()
// save user data in database
withContext(Dispatchers.IO) { userDao.addUserFullProfile(userEntity) }
// convert user entity to user model
val user = userEntity.toUserFullProfile()
return Result.Success(user)
} ?: handleFailure(response)
} else {
handleFailure(response)
}
} catch (e: Exception) {
return Result.Failure(e, e.localizedMessage)
}
} else {
// get user from database if no network
val data = withContext(Dispatchers.IO) { userDao.getUserById(userId) }
return if (data != null) {
Log.d("REPO", "get users from db")
val user = data.toUserFullProfile()
Result.Success(user)
} else {
Result.Failure(Exception("error"), "no network connection")
}
}
}
UserViewModel:
getUserList and getUserFullProfile are use cases which call the repository
class UserViewModel (private val getUserList: GetUserList, private val getUserFullProfile: GetUserFullProfile) : ViewModel() {
val userList = MutableLiveData<List<UserListItem>>()
val userFullProfile = MutableLiveData<UserFullProfile>()
fun getUserList() {
viewModelScope.launch {
when (val result = getUserList.getUserList()) {
is Result.Success -> userList.value = result.successData
is Result.Failure -> result.exception.localizedMessage
}
}
}
fun getUserFullProfile(userId: String) {
viewModelScope.launch {
when (val result = getUserFullProfile.getUserFullProfile(userId)) {
is Result.Success -> userFullProfile.value = result.successData
is Result.Failure -> result.exception.localizedMessage
}
}
}
UserRecyclerAdaper:
class UserRecyclerAdapter(private val context: Context?, val clickListener: UserClickListener) : RecyclerView.Adapter<UserRecyclerAdapter.UserViewHolder>() {
var userList : List<UserListItem> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val inflatedView: UserItemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.user_item, parent, false)
return UserViewHolder(inflatedView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bindUser(position)
}
override fun getItemCount() = userList.size
fun setUsers(users: List<UserListItem>) {
this.userList = users
notifyDataSetChanged()
}
inner class UserViewHolder(private val v: UserItemBinding) : RecyclerView.ViewHolder(v.root) {
fun bindUser(position: Int) {
val item = userList[position]
Log.d("ADAPTER", item.toString())
v.user = item
Picasso.get()
.load(item.picture)
.placeholder(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_background)
.into(v.picture)
v.userClickInterface = clickListener
v.root.setOnClickListener {
clickListener.onItemClick(item)
}
}
}
UserListFragment:
class UserListFragment : Fragment(), UserClickListener {
private val userViewModel by viewModel<UserViewModel>()
private lateinit var userAdapter: UserRecyclerAdapter
private lateinit var viewDataBinding: FragmentUserListBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_list, container, false)
viewDataBinding.lifecycleOwner = this
return viewDataBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
userAdapter = UserRecyclerAdapter(context, this)
recyclerView.adapter = userAdapter
recyclerView.isNestedScrollingEnabled = false
viewDataBinding.viewModel = userViewModel
userViewModel.getUserList()
userViewModel.userList.observe(viewLifecycleOwner, { userList ->
if (userList.isNotEmpty() && userList != null) {
userAdapter.setUsers(userList)
}
})
}
override fun onItemClick(user: UserListItem) {
Log.d("FRAGMENT", user.toString())
userViewModel.getUserFullProfile(user.id)
userViewModel.userFullProfile.observe(viewLifecycleOwner, { userFullProfile ->
Log.d("UFP", userFullProfile.toString())
if (userFullProfile != null) {
(activity as MainActivity).replaceFragment(UserFullProfileFragment.newInstance(userFullProfile),
R.id.fragment_layout, "userFullProfile")
}
})
}
UserFullProfileFragment:
class UserFullProfileFragment : Fragment() {
companion object {
#JvmStatic
fun newInstance(user: UserFullProfile) = UserFullProfileFragment().apply {
arguments = Bundle().apply {
putParcelable("user", user)
}
}
}
private var user: UserFullProfile? = null
private lateinit var mViewDataBinding: FragmentUserFullProfileBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewDataBinding.user = user
notify()
Picasso.get()
.load(mViewDataBinding.user?.picture)
.placeholder(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_background)
.into(mViewDataBinding.picture)
val dateOfBirth = parseDate(mViewDataBinding.user?.dateOfBirth)
mViewDataBinding.dateOfBirth.text = dateOfBirth
val registerDate = parseDate(mViewDataBinding.user?.registerDate)
mViewDataBinding.registerDate.text = registerDate
}
override fun onAttach(context: Context) {
super.onAttach(context)
user = arguments?.getParcelable("user")
Log.d("APP", user.toString())
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
mViewDataBinding = DataBindingUtil.inflate(inflater,
R.layout.fragment_user_full_profile, container, false)
mViewDataBinding.lifecycleOwner = this
return mViewDataBinding.root
}
Thank you :)
Finally, I found a solution :
I pass the user id argment from UserListFragment to UserFullProfileFragment instead of the current user object and I call the external API to get the current user in the UserFullProfileFragment.
This is the final code:
UserListFragment:
override fun onItemClick(user: UserListItem) {
val action = UserListFragmentDirections.actionUserListFragmentToUserFullProfileFragment(user.id)
findNavController().navigate(action)
}
UserFullProfileFragment:
class UserFullProfileFragment : Fragment() {
private lateinit var userID: String
private var mViewDataBinding: FragmentUserFullProfileBinding? = null
private val binding get() = mViewDataBinding!!
private val userViewModel by viewModel<UserViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = userViewModel
userID = UserFullProfileFragmentArgs.fromBundle(requireArguments()).userArgs
userViewModel.getUserFullProfile(userID)
userViewModel.userFullProfile.observe(viewLifecycleOwner, { userFullProfile ->
if (userFullProfile != null) {
binding.user = userFullProfile
Picasso.get()
.load(binding.user?.picture)
.placeholder(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_background)
.into(binding.picture)
val dateOfBirth = parseDate(binding.user?.dateOfBirth)
binding.dateOfBirth.text = dateOfBirth
val registerDate = parseDate(binding.user?.registerDate)
binding.registerDate.text = registerDate
}
})
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
mViewDataBinding = DataBindingUtil.inflate(inflater,
R.layout.fragment_user_full_profile, container, false)
binding.lifecycleOwner = this
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
mViewDataBinding = null
}
}

variable value gets null in retrofit

I have a fragment which has an adapter. I am using retrofit to fetch data from web
my frangment
class MyTransactionFragment : Fragment() {
val disposable = CompositeDisposable()
var transactionDetailList:ArrayList<TransactionData>? = null
private var mApiSocket: ApiSocket? = null
private var mRecyclerView:RecyclerView? = null
var adapter:TransactionAdapter? = null
companion object {
val TAG = ItemFragment::class.java.simpleName!!
fun newInstance(transactionPojo: TransactionPojo): MyTransactionFragment {
val fragment = MyTransactionFragment()
val args = Bundle()
args.putSerializable(Constants.TRANSACTIONS, transactionPojo)
fragment.arguments = args
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_my_transaction, container, false)
mApiSocket = Utils().getApiSocket(activity!!)
val layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
view.mRecyclerViewTransaction.layoutManager = layoutManager
view.mRecyclerViewTransaction.setHasFixedSize(true)
if (arguments!=null && arguments!!.getSerializable(Constants.TRANSACTIONS) != null){
val transactionPojo = arguments!!.getSerializable(Constants.TRANSACTIONS) as TransactionPojo
transactionDetailList = ArrayList(transactionPojo.transactions.data)
adapter = TransactionAdapter(transactionDetailList, activity!!,disposable)
view.mRecyclerViewTransaction.adapter = adapter
mRecyclerView = view.mRecyclerViewTransaction
val scrollListener = object : EndlessRecyclerViewScrollListener(layoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView) {
disposable.add(mApiSocket!!.transaction(""+page+1)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(MyTransactionFragment()::onResponse,MyTransactionFragment()::onError))
}
}
view.mRecyclerViewTransaction.addOnScrollListener(scrollListener)
}
return view
}
private fun onResponse(transactionPojo: TransactionPojo){
transactionDetailList = ArrayList(transactionPojo.transactions.data)
adapter!!.addData(transactionDetailList) //here adapter variable gets null Why
}
fun onError(e: Throwable) {
Log.e("TransactionFragment", e.toString())
if (e is NoNetWorkException) {
Utils().showSnackBar(activity!!, "No Network connection")
} else
Utils().showSnackBar(activity!!, "Something went wrong please try again later")
Utils().hideProgress()
}
override fun onDetach() {
super.onDetach()
disposable.dispose()
}
}
In this adapter variable in the onResponse getting null Why?
Last I found it
Actually I am calling MyTransactionFragment()::onResponse in subscriber. which means I am putting the result on the new instance of MyTransactionFragment. So I changed this to
disposable.add(mApiSocket!!.transaction(""+page+1)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onResponse,::onError))

Categories

Resources