Raise error if passwords don't match on sign up - android

I'm trying to get my sign up fragment to show an error if the passwords don't match but I only see the error that asks for alphanumeric characters (but even if I type that it doesn't seem to work).
What am I doing wrong?
This is the view model where I set up the conditions to raise an error if they aren't met:
class SignUpViewModel(private val signUpRepository: SignUpRepository) : ViewModel() {
private val _signUpForm = MutableLiveData<SignUpFormState>()
val signUpFormState: LiveData<SignUpFormState> = _signUpForm
fun signUp(name: String, email:String, password: String) {
}
fun signUpDataChanged(email:String, password: String, confirmPassword: String) {
if (!isEmailValid(email)) {
_signUpForm.value = SignUpFormState(emailError = R.string.invalid_email)
} else if (!isPasswordValid(password)) {
_signUpForm.value = SignUpFormState(passwordError = R.string.invalid_password_signUp)
} else if (!passwordsMatch(password,confirmPassword)){
_signUpForm.value = SignUpFormState(confirmPasswordError = R.string.mismatched_password)
}
else {
_signUpForm.value = SignUpFormState(isDataValid = true)
}
}
// A placeholder username validation check
private fun isEmailValid(email: String): Boolean {
return if (email.contains('#')) {
Patterns.EMAIL_ADDRESS.matcher(email).matches()
} else {
email.isNotBlank()
}
}
// A placeholder password validation check
private fun isPasswordValid(password: String): Boolean {
return password.contains("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$")
}
//check if passwords are the same
private fun passwordsMatch(password: String, confirmPassword:String): Boolean{
return password == confirmPassword
}
}
This is the fragment:
class SignUpFragment : Fragment() {
private lateinit var binding: FragmentSignupBinding
private lateinit var signUpViewModel: SignUpViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSignupBinding.inflate(inflater, container, false)
val nameInput = binding.etName
val emailInput = binding.etEmail
val passwordInput = binding.etPassword
val confirmPasswordInput = binding.etConfirmPassword
val signupButton = binding.btSignUp
signUpViewModel = ViewModelProvider(this, SignUpViewModelFactory())
.get(SignUpViewModel::class.java)
signUpViewModel.signUpFormState.observe(viewLifecycleOwner, Observer {
val signUpState = it ?: return#Observer
// disable sign up button unless fields are valid
signupButton.isEnabled = signUpState.isDataValid
if (signUpState.emailError != null) {
emailInput.error = getString(signUpState.emailError)
}
if (signUpState.passwordError != null) {
passwordInput.error = getString(signUpState.passwordError)
}
if (signUpState.confirmPasswordError != null) {
confirmPasswordInput.error = getString(signUpState.confirmPasswordError)
}
})
emailInput.afterTextChanged {
signUpViewModel.signUpDataChanged(
emailInput.text.toString(),
passwordInput.text.toString(),
confirmPasswordInput.text.toString()
)
}
passwordInput.afterTextChanged {
signUpViewModel.signUpDataChanged(
emailInput.text.toString(),
passwordInput.text.toString(),
confirmPasswordInput.text.toString()
)
}
confirmPasswordInput.apply {
afterTextChanged {
signUpViewModel.signUpDataChanged(
emailInput.text.toString(),
passwordInput.text.toString(),
confirmPasswordInput.text.toString()
)
}
setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE ->
signUpViewModel.signUp(
emailInput.text.toString(),
passwordInput.text.toString(),
confirmPasswordInput.text.toString()
)
}
false
}
}
return binding.root
}
}
fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
afterTextChanged.invoke(editable.toString())
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
}

String.contains matches substring not regex , for regex use
val regex = Regex("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$")
assertTrue(regex.containsMatchIn("xabcdy"))
For your match function , try logging first then use String.trim() for unexpected spaces.

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

Data From API Has Not Appeared Using Paging 3

I'm learning paging 3, but the data from the API doesn't appear. My code is like below:
interface PokeAPI {
#GET("pokemon")
fun getPokemonList() : Call<PokemonList>
#GET("pokemon")
fun getAllPokemon(
#Query("limit") limit: Int,
#Query("offset") offset: Int) : PokemonList
#GET("pokemon/{name}")
fun getPokemonInfo(
#Path("name") name: String
) : Call<Pokemon>
}
class PokePagingSource(private val apiService: PokeAPI): PagingSource<Int, Result>() {
private companion object {
const val INITIAL_PAGE_INDEX = 1
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
return try {
val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getAllPokemon(position, params.loadSize)
if (responseData.results.isEmpty()) {
Log.e("Response Succeed!", responseData.results.toString())
} else {
Log.e("Response Failed!", responseData.results.toString())
}
LoadResult.Page(
data = responseData.results,
prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
nextKey = if (responseData.results.isNullOrEmpty()) null else position + 1
)
} catch (exception: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, Result>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
class PokemonRepository(private val apiService: PokeAPI) {
fun getAllPokemon(): LiveData<PagingData<Result>>{
return Pager(
config = PagingConfig(
pageSize = 10
),
pagingSourceFactory = {
PokePagingSource(apiService)
}
).liveData
}
}
object Injection {
private val api by lazy { RetrofitClient().endpoint }
fun provideRepository(): PokemonRepository {
return PokemonRepository(api)
}
}
class PokemonViewModel(pokemonRepository: PokemonRepository) : ViewModel() {
val allPokemonList: LiveData<PagingData<Result>> =
pokemonRepository.getAllPokemon().cachedIn(viewModelScope)
}
class ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PokemonViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return PokemonViewModel(Injection.provideRepository()) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
`class PokemonPagingAdapter(private val context: Context) :
PagingDataAdapter<Result, PokemonPagingAdapter.ViewHolder>(DIFF_CALLBACK) {
private var onItemClick: OnAdapterListener? = null
fun setOnItemClick(onItemClick: OnAdapterListener) {
this.onItemClick = onItemClick
}
class ViewHolder(val binding: AdapterPokemonBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
AdapterPokemonBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pokemonData = getItem(position)
if (pokemonData != null) {
holder.binding.apply {
val number = if (pokemonData.url.endsWith("/")) {
pokemonData.url.dropLast(1).takeLastWhile { it.isDigit() }
} else {
pokemonData.url.takeLastWhile { it.isDigit() }
}
val url = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png"
Glide.with(context)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop()
.circleCrop()
.into(ivPokemon)
tvNamePokemon.text = pokemonData.name
btnDetail.setOnClickListener {
onItemClick?.onClick(pokemonData, pokemonData.name, url)
}
}
}
}
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem.name == newItem.name
}
}
}
interface OnAdapterListener {
fun onClick(data: Result, name: String, url: String)
}
}`
class FragmentPokemon: Fragment(R.layout.fragment_pokemon) {
private var _binding : FragmentPokemonBinding? = null
private val binding get() = _binding!!
private lateinit var dataPagingAdapter: PokemonPagingAdapter
private val viewModel: PokemonViewModel by viewModels {
ViewModelFactory()
}
private lateinit var comm: Communicator
override fun onStart() {
super.onStart()
getData()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPokemonBinding.bind(view)
val toolBar = requireActivity().findViewById<View>(R.id.tool_bar)
toolBar.visibility = View.VISIBLE
val navBar = requireActivity().findViewById<BottomNavigationView>(R.id.bottom_navigation)
navBar.visibility = View.VISIBLE
comm = requireActivity() as Communicator
setupListPokemon()
}
private fun setupListPokemon(){
dataPagingAdapter = PokemonPagingAdapter(requireContext())
dataPagingAdapter.setOnItemClick(object: PokemonPagingAdapter.OnAdapterListener{
override fun onClick(data: Result, name: String, url: String) {
comm.passDataCom(name, url)
}
})
binding.apply {
rvPokemon.layoutManager = LinearLayoutManager(context)
rvPokemon.setHasFixedSize(true)
rvPokemon.adapter = dataPagingAdapter
}
}
private fun getData(){
viewModel.allPokemonList.observe(viewLifecycleOwner){
dataPagingAdapter.submitData(lifecycle, it)
binding.btnCoba.setOnClickListener { btn ->
if (it == null){
Log.e("ResponseFailed", it.toString())
} else Log.e("ResponseSucceed", it.toString())
}
}
}
}
What's the reason? I have followed the step by step implementation of paging 3 but the data still doesn't appear either.
I don't know the API you are using, but it seems that you are using it incorrectly. The getAllPokemon method has limit and offset parameters and you are calling it like apiService.getAllPokemon(position, params.loadSize), so you are using position as a limit and params.loadSize as an offset.
You should pass params.loadSize as a limit, rename INITIAL_PAGE_INDEX to INITIAL_OFFSET and set it to 0, since your API uses offsets instead of pages (at least it seems so from what you provided). The load function should then look something like this:
// get current offset
val offset = params.key ?: INITIAL_OFFSET
val responseData = apiService.getAllPokemon(limit = params.loadSize, offset = offset)
val prevKey = offset - params.loadSize
val nextKey = offset + params.loadSize

How can I handle the state value on change without using Composable in Kotlin?

I have a VM and a fragment. I want to handle the change of state.value in the fragment. How can I do that. Here is the fragment:
AndroidEntryPoint
class ImportFragment : Fragment() {
private val importVM by viewModels<ImportFragmentVM>()
lateinit var importButton: Button
lateinit var createButton: Button
lateinit var tab: TabLayout
lateinit var createItems: Group
lateinit var importItems: Group
lateinit var longUrl: EditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.import_fragment, container, false)
importButton = view.findViewById(R.id.importButton)
createButton = view.findViewById(R.id.createButton)
tab = view.findViewById(R.id.tab)
createItems = view.findViewById(R.id.createGroup)
importItems = view.findViewById(R.id.importGroup)
longUrl = view.findViewById(R.id.longurl)
createItems.isVisible = false
Timber.d("tab position:" + tab.selectedTabPosition)
tab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
createItems.isVisible = false
importItems.isVisible = true
Timber.d("position 0")
}
1 -> {
createItems.isVisible = true
importItems.isVisible = false
Timber.d("position 1")
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
longUrl.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(
s: CharSequence, start: Int,
count: Int, after: Int
) {
}
override fun onTextChanged(
s: CharSequence, start: Int,
before: Int, count: Int
) {
importVM.updateInput(longUrl.text.toString())
}
})
createButton.setOnClickListener {
val x = importVM.submit()
if (x is ImportFragmentVM.ImportUIState.Success)
Timber.d("assadads")
}
return view
}
}
And here is the VM for this fragment:
#HiltViewModel
class ImportFragmentVM #Inject constructor(
private val service: UrlService
) : ViewModel() {
sealed interface ImportUIState {
data class PendingUserInput(val longUrl: String) : ImportUIState
object Loading : ImportUIState
object Success : ImportUIState
data class PartialSuccess(val urlKey: UrlKey, val urlApiKey: UrlApiKey) : ImportUIState
object Error : ImportUIState
object Invalid : ImportUIState
}
val _state = MutableStateFlow<ImportUIState>(PendingUserInput(""))
val state: StateFlow<ImportUIState> = _state
fun updateInput(longUrl: String) {
check(_state.value is PendingUserInput) { "You can't be in update" }
Timber.d("longurl:" + longUrl)
_state.value = PendingUserInput(longUrl)
}
fun submit():ImportUIState {
val longUrl = state.value.run {
check(this is PendingUserInput) { "You can't be in update when submitting" }
longUrl
}
Timber.d("sunt in submit")
_state.value = Loading
Timber.d("value is" + state.value)
viewModelScope.launch(Dispatchers.IO) {
val result = service.shortenUrl(longUrl)
if (result is ShortenRequestOutcome.Failed)
_state.value = Error
if (result is ShortenRequestOutcome.Invalid)
_state.value = Invalid
if (result is ShortenRequestOutcome.Success)
_state.value = Success
}
return state.value
}
}
For example I want to show a dialog/toast based on the state.value, and I don't know how to find out in the fragment when the value has changed. Any ideas?
To collect your flow in a Fragment, you can use:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
importVM.state.collect { data ->
//do something
}
}
}

Paging 3 Library calls the load method recursively with LoadType.APPEND

I am trying to display data from IconFinder API. It seems to be ItemKeyedDataSource for me and I used Paging3 to display the data as it's mentioned in the official docs.
Here is code, please check if there're any issues with the implementation I have done and where is the mistake.
IconSetsRemoteMediator
#OptIn(ExperimentalPagingApi::class)
class IconSetsRemoteMediator(
private val query: String?,
private val database: IconsFinderDatabase,
private val networkService: IconFinderAPIService
) : RemoteMediator<Int, IconSetsEntry>() {
private val TAG: String? = IconSetsRemoteMediator::class.simpleName
private val iconSetsDao = database.iconSetsDao
private val remoteKeysDao = database.remoteKeysDao
override suspend fun initialize(): InitializeAction {
// Load fresh data when ever the app is open new
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, IconSetsEntry>
): MediatorResult {
val iconSetID = when (loadType) {
LoadType.REFRESH -> {
null
}
LoadType.PREPEND -> {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
LoadType.APPEND -> {
Log.d(TAG, "LoadType.APPEND")
val lastItem = state.lastItemOrNull()
if (lastItem == null) {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
// Get the last item from the icon-sets list and return its ID
lastItem.iconset_id
}
}
try {
// Suspending network load via Retrofit.
val response = networkService.getAllPublicIconSets(after = iconSetID)
val iconSets = response.iconsets
val endOfPaginationReached = iconSets == null || iconSets.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
// Delete the data in the database
iconSetsDao.deleteAllIconSets()
//remoteKeysDao.deleteRemoteKeys()
}
Log.d(TAG, "iconSets = ${iconSets?.size}")
Log.d(TAG, "endOfPaginationReached = $endOfPaginationReached")
Log.d(TAG, "state.anchorPosition = ${state.anchorPosition}")
Log.d(TAG, "state.pages = ${state.pages.size}")
val time = System.currentTimeMillis()
/*val remoteKeys = iconSets!!.map {
RemoteKeysEntry(it.iconset_id, time)
}*/
// Insert new IconSets data into database, which invalidates the current PagingData,
// allowing Paging to present the updates in the DB.
val data = iconSets!!.mapAsIconSetsEntry()
iconSetsDao.insertAllIconSets(data)
// Insert the remote key values which set the time at which the data is
// getting updated in the DB
//remoteKeysDao.insertRemoteKeys(remoteKeys)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
}
IconFinderRepository
class IconFinderRepository(
private val service: IconFinderAPIService,
private val database: IconsFinderDatabase
) {
private val TAG: String? = IconFinderRepository::class.simpleName
fun getPublicIconSets(): Flow<PagingData<IconSetsEntry>> {
Log.d(TAG, "New Icon Sets query")
val pagingSourceFactory = { database.iconSetsDao.getIconSets() }
#OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(pageSize = NUMBER_OF_ITEMS_TO_FETCH, enablePlaceholders = false),
remoteMediator = IconSetsRemoteMediator(
query = null,
database,
service
),
pagingSourceFactory = pagingSourceFactory
).flow
}
companion object {
const val NUMBER_OF_ITEMS_TO_FETCH = 20
}
}
IconSetViewHolder
class IconSetViewHolder private constructor(val binding: RecyclerItemIconSetBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(iconSetsEntry: IconSetsEntry?) {
if (iconSetsEntry == null) {
//Show the Loading UI
} else {
binding.model = iconSetsEntry
binding.executePendingBindings()
}
}
companion object {
fun from(parent: ViewGroup): IconSetViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RecyclerItemIconSetBinding.inflate(layoutInflater, parent, false)
return IconSetViewHolder(binding)
}
}
}
IconSetAdapter
class IconSetAdapter : PagingDataAdapter<UiModel.IconSetDataItem, ViewHolder>(UI_MODEL_COMPARATOR) {
companion object {
private val UI_MODEL_COMPARATOR =
object : DiffUtil.ItemCallback<UiModel.IconSetDataItem>() {
override fun areContentsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean {
return oldItem.iconSetsEntry.iconset_id == newItem.iconSetsEntry.iconset_id
}
override fun areItemsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean =
oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == R.layout.recycler_item_icon_set) {
IconSetViewHolder.from(parent)
} else {
IconSetViewHolder.from(parent)
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is UiModel.IconSetDataItem -> R.layout.recycler_item_icon_set
null -> throw UnsupportedOperationException("Unknown view")
else -> throw UnsupportedOperationException("Unknown view")
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val uiModel = getItem(position)
uiModel.let {
when (uiModel) {
is UiModel.IconSetDataItem -> (holder as IconSetViewHolder).bind(uiModel.iconSetsEntry)
}
}
}
}
HomeFragmentViewModel
class HomeFragmentViewModel(application: Application) : AndroidViewModel(application) {
private val TAG: String? = HomeFragmentViewModel::class.simpleName
private val repository: IconFinderRepository = IconFinderRepository(
IconFinderAPIService.create(),
IconsFinderDatabase.getInstance(application)
)
private var iconSetsQueryResult: Flow<PagingData<UiModel.IconSetDataItem>>? = null
fun iconSetsQuery(): Flow<PagingData<UiModel.IconSetDataItem>> {
val newResult: Flow<PagingData<UiModel.IconSetDataItem>> = repository.getPublicIconSets()
.map { pagingData -> pagingData.map { UiModel.IconSetDataItem(it) } }
.cachedIn(viewModelScope)
iconSetsQueryResult = newResult
return newResult
}
/**
* Factory for constructing HomeFragmentViewModel
*/
class Factory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(HomeFragmentViewModel::class.java)) {
return HomeFragmentViewModel(application) as T
}
throw IllegalArgumentException("Unable to construct ViewModel")
}
}
}
sealed class UiModel {
data class IconSetDataItem(val iconSetsEntry: IconSetsEntry) : UiModel()
}
IconSetFragment: This is one of the fragments implemented as part of ViewPager. Its parent is a Fragment in an Activity.
class IconSetFragment : Fragment() {
private val TAG: String = IconSetFragment::class.java.simpleName
/**
* Declaring the UI Components
*/
private lateinit var binding: FragmentIconSetBinding
private val viewModel: HomeFragmentViewModel by viewModels()
private val adapter = IconSetAdapter()
private var job: Job? = null
companion object {
fun newInstance(): IconSetFragment {
return IconSetFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Get a reference to the binding object
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_icon_set, container, false)
Log.d(TAG, "onCreateView")
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initAdapter()
job?.cancel()
job = viewLifecycleOwner.lifecycleScope.launch {
viewModel.iconSetsQuery().collectLatest {
adapter.submitData(it)
Log.d(TAG, "collectLatest $it")
}
}
}
private fun initAdapter() {
binding.rvIconSetList.adapter = adapter
/*.withLoadStateHeaderAndFooter(
header = LoadStateAdapter(), // { adapter.retry() },
footer = LoadStateAdapter { adapter.retry() }
)*/
}
}
IconSetsDao
#Dao
interface IconSetsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllIconSets(iconSets: List<IconSetsEntry>)
#Query("SELECT * FROM icon_sets_table")
fun getIconSets(): PagingSource<Int, IconSetsEntry>
#Query("DELETE FROM icon_sets_table")
suspend fun deleteAllIconSets()
}
This is the Logcat screenshot, the load() method is being invoked without any scrolling action.
I have the similar issue, seems the recursive loading issue is fixed by setting the recyclerView.setHasFixedSize(true)

Update current list item

I open the list item, get the data in fetchData(), then I expect that by calling the addTarget() method I will update the current item(name and description). Instead, I create a new one.
Q: How can I update the current one?
class TargetEditFragment : Fragment() {
private var nameEditText: TextInputEditText? = null
private var descriptionEditText: TextInputEditText? = null
private var button: Button? = null
private var databaseReference: DatabaseReference? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.getString(KEY_TARGET_GUID, "")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_target_add, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
databaseReference = FirebaseDatabase.getInstance().getReference("targets")
setupViews()
fetchData(guid = arguments?.getString(KEY_TARGET_GUID, "") ?: "")
}
private fun setupViews() {
nameEditText = view?.findViewById(R.id.nameEditText)
descriptionEditText = view?.findViewById(R.id.descriptionEditText)
button = view?.findViewById(R.id.addNote)
button?.setOnClickListener { addTarget() }
}
private fun addTarget() {
val name = nameEditText?.text.toString().trim()
val description = descriptionEditText?.text.toString().trim()
if (!TextUtils.isEmpty(name)) {
val id: String = databaseReference?.push()?.key.toString()
val target = Target(guid = id, name = name, description = description)
databaseReference?.child(id)?.setValue(target)
} else Log.d("some", "Enter a name")
}
private fun fetchData(guid: String) {
// Attach a listener to read the data at the target id
databaseReference?.child(guid)?.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.value as HashMap<String, String>
val name = data["name"] ?: ""
val description = data["description"] ?: ""
if (name.isEmpty()) Log.d("some", "nameIsEmpty")
else {
updateViewsContent(name = name, description = description)
}
}
override fun onCancelled(p0: DatabaseError) {
Log.d("some", "onCancelled")
}
})
}
private fun updateViewsContent(name: String?, description: String?) {
nameEditText?.text = Editable.Factory.getInstance().newEditable(name)
descriptionEditText?.text = Editable.Factory.getInstance().newEditable(description)
}
companion object {
fun newInstance(guid: String): TargetEditFragment =
TargetEditFragment().apply {
arguments = Bundle().apply { putString(KEY_TARGET_GUID, guid) }
}
}
}
There is no way to update an element using the push() method because everytime you call this method a new unique key is generated. In order to perform an update you need to know the key of the element you want to update and use it in your reference. For more informations, please see my answer from the following post:
How to get specific pushedID in Firebase?

Categories

Resources