Observing class parameters in Android using databinding and Kotlin - android

I have a model
data class RegisterPostDataWithPwdCheck(
var phone_number: String?,
var name: String?,
var password: String?,
var secondPassword: String?)
And a ViewModel
class SignUpViewModel(application: Application) : BaseViewModel(application){
val registerPostData = MutableLiveData<RegisterPostDataWithPwdCheck>...
fun checkPassword(){}...}
I also have a View that has this code inside
viewModel.registerPostData.observe(viewLifecycleOwner, Observer {
viewModel.checkPassword()
})
In the XML there are two fields of interest
<EditText
android:id="#+id/edittext_sign_up_password"
android:text="#={view_model.registerPostData.password}" />
<EditText
android:id="#+id/edittext_sign_up_second_pw"
android:text="#={view_model.registerPostData.secondPassword}" />
What I understood so far is that the .observe will be called only when the entire RegisterPostDataWithPwdCheck object changes and I don't want that. I want it to be triggered when any of the parameters changes so I can call the fun checkPassword(){} in order to see if the two fields match. Is this possible?

Using #mahdi-shahbazi comment I've managed to work this out in Kotlin. My Model is now:
data class RegisterPostDataWithPwdCheck(
#SerializedName(value = "phone_number")
private var phoneNumber: String?,
private var name: String?,
private var password: String?,
private var secondPassword: String?
) : BaseObservable() {
#Bindable
fun getPhoneNumber(): String? {
return phoneNumber
}
fun setPhoneNumber(value: String) {
if (value != phoneNumber) {
phoneNumber = value
notifyPropertyChanged(BR.phoneNumber)
}
}
#Bindable
fun getName(): String? {
return name
}
fun setName(value: String?) {
if (value != name) {
name = value
notifyPropertyChanged(BR.name)
}
}
#Bindable
fun getPassword(): String? {
return password
}
fun setPassword(value: String?) {
if (value != password) {
password = value
notifyPropertyChanged(BR.password)
}
}
#Bindable
fun getSecondPassword(): String? {
return secondPassword
}
fun setSecondPassword(value: String?) {
if (value != secondPassword) {
secondPassword = value
notifyPropertyChanged(BR.secondPassword)
}
}
}
And creating custom LiveData class:
class PropertyAwareMutableLiveData<T : BaseObservable> : MutableLiveData<T>()
{
private val callback = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
value = value
}
}
override fun setValue(value: T?) {
super.setValue(value)
value?.addOnPropertyChangedCallback(callback)
}
}
What I still don't know if there is a way to automate this #Binding process which is terribly slow and boring and also forces some changes (turning parameters to private).

Related

How to use different classes with only one LiveData list?

I have this interface and 2 classes, I know it's not the best approach here, but that's not the point now.
interface FilterItem {
val id: String
val name: String
val isChecked: ObservableField<Boolean>
fun reset()
}
data class Technology(
override val id: String,
override val name: String,
override val isChecked: ObservableField<Boolean> = ObservableField(),
) : FilterItem {
override fun reset() {
isChecked.set(false)
}
}
data class Project(
override val id: String,
override val name: String,
override val isChecked: ObservableField<Boolean> = ObservableField(),
) : FilterItem {
override fun reset() {
isChecked.set(false)
}
}
And then I have a ViewModel
private val _filterList = MutableLiveData<List<FilterItem>>().apply {
viewModelScope.launch {
value = loadSkills()
}
}
val filterList: LiveData<List<FilterItem>>
get() = _filterList
private fun loadSkills(): MutableList<FilterItem> {
val technologiesList: MutableList<FilterItem> = mutableListOf()
technologiesList.add(Technology("1", "Android"))
technologiesList.add(Technology("2", "Kotlin"))
return technologiesList
}
private fun loadProjects(): MutableList<FilterItem> {
val projectsList: MutableList<FilterItem> = mutableListOf()
projectsList.add(Project("1", "Project 1"))
projectsList.add(Project("2", "Project 2"))
return projectsList
}
I want to use the filterList so that is holds a list of Technologies or a list of Projects, depending on what BottomSheetDialog I'm opening.
showSkillsButton.setOnClickListener {
showFilterModal(filterViewModel.filterList, SKILLS)
}
showProjectsButton.setOnClickListener {
showFilterModal(filterViewModel.projects, PROJECTS)
}
I want to somehow use filterList only(I had 2 lists for skills and projects, but it would be nice if I could replace them with only 1 filterList).
Is there any way to do this? Or what would be the best approach?

how to implement search viewmodel and show it in recyclerview in kotlin

I am developing tvshows app where I am implementing following logic user search tvshows and filtered result has to show in recyclerview but I want to implement filtering functionality in viewmodel
how can I achieve that
below interface class
interface ApiInterface {
#GET("search/shows")
suspend fun searchShows( #Query("q") query: String): Call<TvMazeResponse>
}
below TvRepository.kt
class TvRepository(private val apiInterface: ApiInterface) {
suspend fun getShows() = apiInterface.searchShows("")
}
below adapter class
class TvAdapter : RecyclerView.Adapter<TvAdapter.ViewHolder>(), Filterable {
lateinit var tvMazeList: MutableList<TvMazeResponse>
lateinit var filterResult: ArrayList<TvMazeResponse>
override fun getItemCount(): Int =
filterResult.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.tv_item, parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(filterResult[position])
}
fun addData(list: List<TvMazeResponse>) {
tvMazeList = list as MutableList<TvMazeResponse>
filterResult = tvMazeList as ArrayList<TvMazeResponse>
notifyDataSetChanged()
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charString = constraint?.toString() ?: ""
if (charString.isEmpty()) filterResult =
tvMazeList as ArrayList<TvMazeResponse> else {
val filteredList = ArrayList<TvMazeResponse>()
tvMazeList
.filter {
(it.name.contains(constraint!!)) or
(it.language.contains(constraint))
}
.forEach { filteredList.add(it) }
filterResult = filteredList
}
return FilterResults().apply { values = filterResult }
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
filterResult = if (results?.values == null)
ArrayList()
else
results.values as ArrayList<TvMazeResponse>
notifyDataSetChanged()
}
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(result: TvMazeResponse) {
with(itemView) {
Picasso.get().load(result.image.medium).into(imageView)
}
}
}
}
below Constants.kt
object Constants {
const val BASE_URL = "https://api.tvmaze.com/"
}
below TvMazeResponse.kt
data class TvMazeResponse(
#SerializedName("averageRuntime")
val averageRuntime: Int,
#SerializedName("dvdCountry")
val dvdCountry: Any,
#SerializedName("externals")
val externals: Externals,
#SerializedName("genres")
val genres: List<String>,
#SerializedName("id")
val id: Int,
#SerializedName("image")
val image: Image,
#SerializedName("language")
val language: String,
#SerializedName("_links")
val links: Links,
#SerializedName("name")
val name: String,
#SerializedName("network")
val network: Network,
#SerializedName("officialSite")
val officialSite: String,
#SerializedName("premiered")
val premiered: String,
#SerializedName("rating")
val rating: Rating,
#SerializedName("runtime")
val runtime: Int,
#SerializedName("schedule")
val schedule: Schedule,
#SerializedName("status")
val status: String,
#SerializedName("summary")
val summary: String,
#SerializedName("type")
val type: String,
#SerializedName("updated")
val updated: Int,
#SerializedName("url")
val url: String,
#SerializedName("webChannel")
val webChannel: Any,
#SerializedName("weight")
val weight: Int
)
below TvViewModel.kt
class TvViewModel(apiInterface: ApiInterface) : ViewModel() {
}
I want to implement filter and search function in viewmodel how can I achieve that any help and tips greatly appreciated
In TvRepository change the getShows function to
suspend fun getShows(searchString:String) = apiInterface.searchShows(searchString)
Then in the ViewModel change the constructor to get an instance of the TVRepository and call API as shown below
class TvViewModel( tvRepository: TvRepository) : ViewModel() {
fun getShows(searchParameter:String){
viewModelScope.launch(Dispatchers.IO){
val response= tvRepository.getShows().awaitResponse()
if(response.isSuccessful{
//api success you can get result from response.body
}
else{
//api failed
}
}
}
}

ViewModel does not trigger observer of mutablelivedata

I have the following ViewModel class -
class VerifyOtpViewModel : ViewModel() {
private var existingUserProfileData: MutableLiveData<TwoVerteUsers.TwoVerteUser>? = null
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(authorization, GetUserProfilesBodyModel(listOf(user)), object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData?.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
fun getExistingUserProfileData(): LiveData<TwoVerteUsers.TwoVerteUser>? {
if (existingUserProfileData == null) return null
return existingUserProfileData as LiveData<TwoVerteUsers.TwoVerteUser>
}
}
and the following observer -
private fun initViewModel() {
verifyOtpViewModel = ViewModelProvider(this).get(VerifyOtpViewModel::class.java)
verifyOtpViewModel.getExistingUserProfileData()?.observe(this, Observer {
if (it != null)
Log.d("existingProfile", it.username)
})
}
For some reason the observe is never triggered even after the MutableLiveData object is being given a value
Tried to search for a solution here at stackoverflow but nothing helped
what am I missing?
refactor your code to this, and you should be good to go:
class VerifyOtpViewModel : ViewModel() {
private val _existingUserProfileData = MutableLiveData<TwoVerteUsers.TwoVerteUser>()
val existingUserProfileData: LiveData<TwoVerteUsers.TwoVerteUser>
get() = _existingUserProfileData
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(
authorization,
GetUserProfilesBodyModel(listOf(user)),
object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
}
And observing:
verifyOtpViewModel.existingUserProfileData.observe(this, Observer {
.....
})

How to get list of object from flowable object using RxJava

I have got 2 data classes:
GitResult
data class GitResult (
#SerializedName("total_count")
#Expose
var total_count: Int,
#SerializedName("incomplete_results")
#Expose
var incomplete_results: Boolean,
#SerializedName("items")
#Expose
var items: MutableList<ItemList>
){}
and ItemList
data class ItemList (
#SerializedName("id")
#Expose
var id: Int,
#SerializedName("name")
#Expose
var name: String,
#SerializedName("language")
#Expose
var language: String?,
#SerializedName("description")
#Expose
var description: String?,
#SerializedName("html_url")
#Expose
var html_url: String?
){}
with retrofit I make a call:
#GET("repositories")
fun getAllRepo2(#Query("q") q: String? ,
#Query("sort") sort: String? ,
#Query("order") order: String?
) : Observable<GitResult>
then return it to my viewModel class as Flowable object.
val ResultFromApiCall_flowable = mainRepository.fetchToDosFromServer(filterDate, filterStatus, filterName)
So, now I have a flowable object of GitResult. But how do I get the list of ItemList objects using both RxJava .
I tried using map operator:
var lst = mutableListOf<ItemList>()
var yy = ResultFromApiCall_flowable.map {//it = gitResult
gitResult ->
gitResult.items.forEach {
lst.add(it)
}
lst
} // returns Observable<MutableList<ItemList>>
yy.observeOn(AndroidSchedulers.mainThread()).subscribeOn(AndroidSchedulers.mainThread()).subscribe { //it:MutableList<ItemList>
object: Observer<MutableList<ItemList>>{
override fun onComplete() {
Log.d("myLog","------------test3 ")
}
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) { }
override fun onNext(t: MutableList<ItemList>) {
Log.d("myLog","------------test3 $t")
}
}}
But seems that its not working. How do i solve it?
You are doing the network call on main thread change thesubscribeOn(AndroidSchedulers.mainThread()) to subscribeOn(Schedulers.io())
so
yy.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe { //it:MutableList<ItemList>
object: Observer<MutableList<ItemList>>{
override fun onComplete() {
Log.d("myLog","------------test3 ")
}
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) { }
override fun onNext(t: MutableList<ItemList>) {
Log.d("myLog","------------test3 $t")
}
}}

How to test sharedpreference that inject in repository in Android MVP Clean Architecture

i've facing a problem to test sharedpreference in datastore. in actual datastore i implement three arguments, those include sharedpreference.
in this case i want to store value, and get that value. mocking not help here.
mocking cannot propagate actual value, that will be used by code. in second part.
class FooDataStoreTest : Spek({
given("a foo data store") {
val schedulerRule = TestSchedulerRule()
val service: FooRestService = mock()
val context: Context = mock()
val gson: Gson = mock()
val appFooPreference: SharedPreferences = mock()
var appFooSessionStoreService: AppFooSessionStoreService? = null
var fooStoredLocationService: FooStoredLocationService? = null
beforeEachTest {
appFooSessionStoreService = AppFooSessionStoreService.Builder()
.context(context)
.gson(gson)
.preference(appFooPreference)
.build()
fooStoredLocationService = FooStoredLocationService(appFooSessionStoreService)
}
val longitude = 106.803090
val latitude = -6.244285
on("should get foo service with request longitude $longitude and latitude $latitude") {
it("should return success") {
with(fooStoredLocationService) {
val location = Location()
location.latitude = latitude
location.longitude = longitude
// i want to store location in this
fooStoredLocationService?.saveLastKnownLocation(location)
// and retrieve in below code
val l = fooStoredLocationService?.lastKnownLocation
val dataStore = FooDataStore(service, preference, fooStoredLocationService!!)
service.getFooService(longitude, longitude) willReturnJust
load(FooResponse::class.java, "foo_response.json")
val testObserver = dataStore.getFooService().test()
schedulerRule.testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
testObserver.assertNoErrors()
testObserver.awaitTerminalEvent()
testObserver.assertComplete()
testObserver.assertValue { actual ->
actual == load(FooResponse::class.java, "foo_response.json")
}
}
}
}
afterEachTest {
appFooSessionStoreService?.clear()
fooStoredLocationService?.clear()
}
}})
and this datastore looks like
open class FooDataStore #Inject constructor(private val fooRestService: FooRestService,
private val fooPreference: FooPreference,
private val fooLocation: fooStoredLocationService) : FooRepository {
private val serviceLocation by lazy {
fooLocation.lastKnownLocation
}
override fun getFooService(): Single<FooResponse> {
safeWith(serviceLocation, {
return getFooLocal(it).flatMap({ (code, message, data) ->
if (data != null) {
Single.just(FooResponse(code, message, data))
} else {
restService.getFooService(it.longitude, it.latitude).compose(singleIo())
}
})
})
return Single.error(httpExceptionFactory(GPS_NOT_SATISFYING))
}
}
Actually i want to get value in from this field serviceLocation. Anyone has approach to do some test for that?, any advise very welcome.
thanks!
I would recommend you not to depend on SharedPreferences directly, but to have some interface LocalStorage, so you can have your SharedPrefsLocalStorage being used in the code and TestLocalStorage in the tests. SharedPrefsLocalStorage will use SharedPreferences under the hood, and TestLocalStorage some Map implementation.
Just a simple example:
// You may add other type, not Int only, or use the String and convert everything to String and back
interface LocalStorage {
fun save(key: String, value: Int)
fun get(key: String): Int?
}
class SharedPrefsLocalStorage(val prefs: SharedPreferences) : LocalStorage {
override fun save(key: String, value: Int) {
with(prefs.edit()){
putInt(key, value)
commit()
}
}
override fun get(key: String): Int? = prefs.getInteger(key)
}
class TestLocalStorage : LocalStorage {
val values = mutableMapOf<String, Any>()
override fun save(key: String, value: Int) {
values[key] = value
}
override fun get(key: String): Int? = map[value] as Int?
}

Categories

Resources