I want to get input from the user using EditText and pass it to server and show the response to the user. I do this simply without any architecture but I would like to implement it in MVVM.
this is my repository code:
class Repository {
fun getData(context: Context, word: String): LiveData<String> {
val result = MutableLiveData<String>()
val request = object : StringRequest(
Method.POST,
"https://jsonplaceholder.typicode.com/posts",
Response.Listener {
result.value = it.toString()
},
Response.ErrorListener {
result.value = it.toString()
})
{
#Throws(AuthFailureError::class)
override fun getParams(): MutableMap<String, String> {
val params = HashMap<String, String>()
params["word"] = word
return params
}
}
val queue = Volley.newRequestQueue(context)
queue.add(request)
return result
}
}
and these are my View Model codes:
class ViewModel(application: Application) : AndroidViewModel(application) {
fun getData(word: String): LiveData<String> {
val repository = Repository()
return repository.getData(getApplication(), word)
}
}
and my mainActivity would be like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this).get(ViewModel::class.java)
model.getData("test").observe(this, Observer {
Log.i("Log", "activity $it")
})
}
}
My layout has an EditText which I want to get user input and pass it to the server, how should i do that?
Here how i did it in my projet.
You can probably use android annotations.
It's gonna requiere you to put some dependencies and maybe change the class a bit, but then you gonna link your Viewmodel with your repository and then you gonna have to program the setter of the variable to do a notifyChange() by making the class herited from BaseObservable. Then in the xml, if you did the correctly, you should be able to do a thing like text:"#={model.variable}" and it should be updating at the same time.
A bit hard and explain or to show for me sorry, but i would look into Android Annotations with #DataBinding, #DataBound :BaseObservable
https://github.com/androidannotations/androidannotations/wiki/Data-binding-support
Hope that can help a bit!
Related
I'm newbie in coding, so I want to ask more experienced programmers how to do it right.
I have 2 functions, first from Facebook SDK and second from AppsFlyerLib.
Can you tell me if there is right option to wait results from this functions please.
Here example of code:
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
var linkdataPlusApps = ""
//Getting Link Data
val linkdataHandler = object : AppLinkData.CompletionHandler {
override fun onDeferredAppLinkDataFetched(appLinkData: AppLinkData?) {
linkdataPlusApps += appLinkData.toString()
}
}
AppLinkData.fetchDeferredAppLinkData(this, linkdataHandler)
//Gettings Apps
val appsdataHandler = object : AppsFlyerConversionListener {
override fun onConversionDataSuccess(p0: MutableMap<String, Any>?) {
linkdataPlusApps += p0.toString()
}
override fun onConversionDataFail(p0: String?) {}
override fun onAppOpenAttribution(p0: MutableMap<String, String>?) {}
override fun onAttributionFailure(p0: String?) {}
}
AppsFlyerLib.getInstance().init("APPS_KEY", appsdataHandler, this).start(this)
//Here I wanna take this data and put in another activity
val intent = Intent(this, NextActivity::class.java)
intent.putExtra("FetchedData", linkdataPlusApps)
startActivity(intent)
}}
But this code doesn't work because Activity started before data was fetched so string is empty.
I solved this by chaining code, but I'm sure that is dirty-coding.
How I did
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
var linkdataPlusApps = ""
//Getting Link Data
val linkdataHandler = object : AppLinkData.CompletionHandler {
override fun onDeferredAppLinkDataFetched(appLinkData: AppLinkData?) {
linkdataPlusApps += appLinkData.toString()
val appsdataHandler = object : AppsFlyerConversionListener {
override fun onConversionDataSuccess(p0: MutableMap<String, Any>?) {
linkdataPlusApps += p0.toString()
val intent = Intent(this#MainActivity, NextActivity::class.java)
intent.putExtra("FetchedData", linkdataPlusApps)
startActivity(intent)
}
override fun onConversionDataFail(p0: String?) {}
override fun onAppOpenAttribution(p0: MutableMap<String, String>?) {}
override fun onAttributionFailure(p0: String?) {}
}
AppsFlyerLib.getInstance().init("APPS_KEY", appsdataHandler, this#MainActivity).start(this#MainActivity)
}
}
AppLinkData.fetchDeferredAppLinkData(this, linkdataHandler)
}
}
So code starts from fetching AppLinkData, and when fetched, starting fetching apps, and only then starting second activity with right string.
Can I do it in another way?
I have a issue in getting a list returned in observe event in my activity. i am developing a login screen in MVVM. viewmodel is as follows.
my problem is i can get returned data in observe call back into a UI control. but same data returned assign into a list variable is empty. in other words, list returned unable to pass into a a list variable in an activity.
class LoginViewModel #Inject internal constructor (private val loginRepository: LoginRepository,private val usersRepository: UsersRepository): ViewModel() {
private var _userEmail:MutableLiveData<String>
private var _userPassword:MutableLiveData<String>
private var _userLoginData:MutableLiveData<UserLoginData>
private var allUsers:MutableLiveData<List<Users>>
private var findUser:MutableLiveData<List<Users>>
init{
_userEmail= MutableLiveData()
_userPassword= MutableLiveData()
_userLoginData= MutableLiveData()
allUsers= MutableLiveData()
findUser= MutableLiveData()
}
fun getEmail():LiveData<String>{
return _userEmail
}
fun getPassword():MutableLiveData<String>{
return _userPassword
}
fun userLogin(userEmail:String,userPassword:String):MutableLiveData<UserLoginData>{
_userEmail.postValue(userEmail)
_userPassword.postValue(userPassword)
viewModelScope.launch(Dispatchers.IO) {
var userlogindata:UserLoginData=loginRepository.userLogin(userEmail,userPassword)
_userLoginData.postValue(userlogindata)
}
return _userLoginData
}
fun getAllUsers():MutableLiveData<List<Users>>{
//lateinit var _allUsers:List<Users>
viewModelScope.launch(Dispatchers.IO) {
val _allUsers:List<Users> =usersRepository.getUsers()
allUsers.postValue(_allUsers)
}
return allUsers
}
fun findUser(userEmail:String):MutableLiveData<List<Users>>{
//lateinit var finduser:List<Users>
viewModelScope.launch(Dispatchers.IO) {
val _findUser:List<Users> =usersRepository.findUser(userEmail)
findUser.postValue(_findUser)
}
return findUser
}
}
in an activity i am observing the users list and getting the list into a list variable in the activity. code in the activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var loginViewModel: LoginViewModel
lateinit var loginData:UserLoginData
var users:List<Users> = emptyList()
var findUser:List<Users> = emptyList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
loginViewModel = ViewModelProvider(this).get(LoginViewModel::class.java)
/*observe users list*/
loginViewModel.getAllUsers().observe(this, {It->
users=It
binding.textView.text=It[0].email.toString()
})
loginViewModel.findUser(binding.loginEditTextTextEmailAddressTxt.toString().trim()).observe(this,{it->
findUser=it
})
This program failed if i use data in the users or findUser lists.
Kindly help me to find the best practice in getting the changed data from viewmodel into an activity
ViewModel:
data class User(
var name: String
)
private val _allUsers = MutableLiveData<List<User>>()
private val allUsers: LiveData<List<User>> get() = _allUsers
fun fetchAllUsers(): LiveData<List<User>> {
viewModelScope.launch {
//delay is simulating network request delay
delay(1000)
//listOf is simulating usersRepository.getUsers()
_allUsers.value = listOf(User("name1"), User("name2"), User("name3"))
}
return allUsers
}
Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.fetchAllUsers().observe(viewLifecycleOwner) { userList ->
userList.forEach {
Log.d("user", it.name)
}
}
You can try this way but I do not prefer returning liveData with function because you have to observe liveData once. You need to be careful observe once.
Hello I have recently encountered a situation where observing is not possible in livedata, so I am going to ask a question
It's too basic, but I don't know why it doesn't work, so I need your help.
If you give me a little teaching, I would be grateful
my SignUpActivity
class SignUpActivity : BaseKotlinActivity<ActivitySignUpBindingImpl, SignUpViewModel>() {
override val layoutResourceId: Int get() = R.layout.activity_sign_up
override val viewModel: SignUpViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivitySignUpBindingImpl>(this, layoutResourceId)
binding.apply {
lifecycleOwner = this#SignUpActivity
signUpViewModel = viewModel
}
viewModel?.apply {
signUpStep.observe(this#SignUpActivity, Observer {
when (it) {
SignUpStep.SIGN_UP -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpFragment(), "SignUpFragment").commit()
SignUpStep.PASSWORD -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPasswordFragment(), "SignUpPasswordFragment").commit()
SignUpStep.PHONE_CERTIFICATION -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPhoneCertificationFragment(), "SignUpPhoneCertificationFragment").commit()
else -> Unit
}
Log.d("Test Checked1", "${signUpStep.value}")
})
}
}
}
my viewModel
private val _signUpStep = MutableLiveData<SignUpStep>(SignUpStep.SIGN_UP)
val signUpStep: LiveData<SignUpStep>
get() = _signUpStep
fun moveStep(view: View, newSignUpStep: SignUpStep) {
val oldSignUpStep = _signUpStep.value
_signUpStep.value = newSignUpStep
Log.d( "Test Checked","moveStep: $oldSignUpStep -> $newSignUpStep")
}
log
Test Checked1: SIGN_UP
moveStep: SIGN_UP -> PASSWORD
moveStep: PASSWORD -> PASSWORD
moveStep: PASSWORD -> PASSWORD
If you check the log, you can see that the moveStep changes normally. Then signUpSteop has changed normally, but it is not received in the observe of livedata, because the screen does not move and the log does not appear.
I'm just wondering if the code is wrong or what's wrong. Can you help me?
For reference, signUpStep is changing in Fragment and livedata is constantly being observed in activity.
I'm new in kotlin and I'm trying to use retrofit with Rxjava and live data in MVVM architecture.
I config retrofit, and also use observable and subscribe in ViewModel to make observable variable to use in activity binding layout.
I have a button in my view and when I click on it, the request method gets to start and subscription write a log of its own data. but my variable gets null at first and after seconds, when retrofit completed its task, my variable gets data but log value doesn't update.
this is my retrofit initialize class
class ApiService {
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
private fun <T> callBack(iDataTransfer: IDataTransfer<T>) =
object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val data = response.body()
if (data != null)
iDataTransfer.onSuccess(data)
else
try {
throw Exception("data is empty")
} catch (ex: Exception) {
iDataTransfer.onError(ex)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
iDataTransfer.onError(t)
}
}
fun getCategories(iDataTransfer: IDataTransfer<List<Category>>) =
INSTANCE.getCategories().enqueue(callBack(iDataTransfer))
this is an interface for retrofit
#GET("GetCategories")
fun getCategories(): Call<List<Category>>
this is my model class. I think the problem is here. because the observable send null data before retrofit finish its work.
fun getCategories(): Observable<ArrayList<Category>> {
val result = arrayListOf<Category>()
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
result.addAll(data)
}
override fun onError(throwable: Throwable) {
Log.e("Model", throwable.message!!)
}
})
return Observable.just(result)
}
and this is also my ViewModel class
class ProductViewModel(private val model: ProductModel) : ViewModel() {
var isLoading = ObservableField(false)
var categoryList = MutableLiveData<ArrayList<Category>>()
private var compositeDisposable = CompositeDisposable()
fun getCategories() {
isLoading.set(true)
compositeDisposable +=
model.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
categoryList.value = it
}, {
Log.e("ViewModel", it.message.toString())
})
isLoading.set(false)
}
finally, it's my activity
lateinit var binding: ActivityMainBinding
private val vm: ProductViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.vm = vm
vm.categoryList.observe(this, Observer {
if (it != null)
Log.e("activity", it.toString())
})
}
As ExpensiveBelly mentioned in a comment, Retrofit provides a call adapter for RxJava, so you can let your API return Observable<List<Category>> directly. To do this, you will need to add the RxJava call adapter dependency to your app module's build.gradle:
implementation 'com.squareup.retrofit2:adapter-rxjava2:(version)'
Add the call adapter factory when constructing your Retrofit instance:
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // add this
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
And then just let your service return an Observable directly:
#GET("GetCategories")
fun getCategories(): Observable<List<Category>>
If ApiService needs to do some handling before the rest of the app gets the response, you can use RxJava operators like map.
It would be illustrative to see why your code doesn't work and how to fix it. When you call api.getCategories(someCallback), one of your callback methods will be called at some point in the future. In the meantime, the model.getCategories() method will return immediately.
When you subscribe to the returned Observable, it emits the result variable, which is currently an empty list. result will eventually have some data in it, but your code will not be informed of this at all.
What you really want to do is emit the list of categories when it becomes available. The standard way to get an Observable from a callback API is with Observable.create:
fun getCategories(): Observable<ArrayList<Category>> {
return Observable.create { emitter ->
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
emitter.onNext(data)
emitter.onComplete()
}
override fun onError(throwable: Throwable) {
emitter.onError(throwable)
}
})
}
}
Of course, it's better to just use RxJava2CallAdapterFactory if possible, since this work has already been done there.
I want to send some data to the server and show its response to the user.
I'm using MVVM so I created a repository like this:
class Repository {
fun getData(context: Context, word: String): LiveData<String> {
val result = MutableLiveData<String>()
val request = object : StringRequest(
Method.POST,
"https://.......",
Response.Listener {
result.value = it.toString()
},
Response.ErrorListener {
result.value = it.toString()
}) {
#Throws(AuthFailureError::class)
override fun getParams(): MutableMap<String, String> {
val params = HashMap<String, String>()
params["word"] = word
return params
}
}
val queue = Volley.newRequestQueue(context)
queue.add(request)
return result
}
}
which just sends 'word' to the server and get its response.
my view model class contains just a mutableLiveData and a function. it is like this:
class ViewModel(application: Application) : AndroidViewModel(application) {
var result = MutableLiveData<String>()
fun getData(word: String): LiveData<String> {
val repository = Repository()
result = repository.getData(getApplication(), word) as MutableLiveData<String>
return result
}
}
I set an observation for result in my main Activity, therefore it is my MainActivity codes:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this).get(ViewModel::class.java)
submit.setOnClickListener {
model.getData(search_txt.text.toString())
}
model.result.observe(this, Observer {
Log.i("Log", "observe is :$it")
text.text = it
})
}
but it doesn't work! I get the user's input using an edit text and after pressing a button, I call getData function which is in my View Model class. but it returns always null and observation won't work.
I try to put observe the method in my button listener, in this way I get the result but it seems it's not a correct way because after I rotate my phone, all data ware gone and I need to fetch data from the server again while it shouldn't.