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.
Related
In the application, I am fetching data from the web and from the observer change method, Insert that data to local db. that's fine. but after inserted to db, My second observer not called so my UI will not update.
ManActivity.class
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var adapter: MainAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout.activity_main)
setupViewModel()
setupUI()
setupObservers()
setupObservers2()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(ApiHelper(RetrofitBuilder.apiService))
).get(MainViewModel::class.java)
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = MainAdapter(arrayListOf())
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObservers() {
viewModel.getUsers().observe(this, Observer {
//viewModel.getUserFromWeb()
it?.let { resource ->
when (resource.status) {
SUCCESS -> {
Log.d("MYLOG","MyAPIChange success")
recyclerView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
resource.data?.let {
users -> viewModel.setUserListToDB(this,users)
//sleep(1000)
}
}
ERROR -> {
recyclerView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
Log.d("MYLOG","MyAPIChange error")
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
LOADING -> {
Log.d("MYLOG","MyAPIChange loading")
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
}
}
})
}
private fun setupObservers2() {
viewModel.getUserFromDB(this).observe(this, Observer {
users -> retrieveList(users)
Log.d("MYLOG","..MyDBChange")
})
}
private fun retrieveList(users: List<User>) {
adapter.apply {
addUsers(users)
notifyDataSetChanged()
}
}
}
MyViewModel.class
class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {
//lateinit var tempUser : MutableLiveData<List<User>>
fun getUsers() = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
emit(Resource.success(data = mainRepository.getUsers()))
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
//emit(mainRepository.getUsers()) //direct call
}
fun getUserFromDB(context: Context) = liveData(Dispatchers.IO) {
emit(mainRepository.getUserList(context))
}
fun setUserListToDB(context: Context, userList: List<User>) {
/*GlobalScope.launch {
mainRepository.setUserList(context, userList)
}*/
CoroutineScope(Dispatchers.IO).launch {
mainRepository.setUserList(context, userList)
}
}
}
MyRepository.class
class MainRepository(private val apiHelper: ApiHelper) {
suspend fun getUsers() = apiHelper.getUsers() // get from web
companion object {
var myDatabase: MyDatabase? = null
lateinit var userList: List<User>
fun initializeDB(context: Context): MyDatabase {
return MyDatabase.getDataseClient(context)
}
/*fun insertData(context: Context, username: String, password: String) {
myDatabase = initializeDB(context)
CoroutineScope(Dispatchers.IO).launch {
val loginDetails = User(username, password)
myDatabase!!.myDao().InsertData(loginDetails)
}
}*/
}
//fun getUserList(context: Context, username: String) : LiveData<LoginTableModel>? {
suspend fun getUserList(context: Context) : List<User> {
myDatabase = initializeDB(context)
userList = myDatabase!!.myDao().getUserList()
Log.d("MYLOG=", "DBREAD"+userList.size.toString())
return userList
}
fun setUserList(context: Context,userList: List<User>){
myDatabase = initializeDB(context)
/*CoroutineScope(Dispatchers.IO).launch {
myDatabase!!.myDao().InsertAllUser(userList)
Log.d("MYLOG","MyDBInserted")
}*/
myDatabase!!.myDao().InsertAllUser(userList)
Log.d("MYLOG","MyDBInserted")
/*val thread = Thread {
myDatabase!!.myDao().InsertAllUser(userList)
}
Log.d("MYLOG","MyDBInserted")
thread.start()*/
}
}
DAO class
#Dao
interface DAOAccess {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun InsertAllUser(userList: List<User>)
// #Query("SELECT * FROM User WHERE Username =:username")
// fun getLoginDetails(username: String?) : LiveData<LoginTableModel>
#Query("SELECT * FROM User")
suspend fun getUserList() : List<User>
}
RetrofitBuilder
object RetrofitBuilder {
private const val BASE_URL = "https://5e510330f2c0d300147c034c.mockapi.io/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: ApiService = getRetrofit().create(ApiService::class.java)
}
Please can you know that what I am doing wrong here and why the second observer was not called after insert to db
Actually It was called when the screen launched but that time data not inserted so list size was 0 and after insert data this method will not call again. But Once I close app and again start then data will display bcoz at launch time, this method call and data got
I don't have enough reputation to commet, therefore I just bring a suggestion in this answer:
Suggestion/Solution
Room supports LiveData out of the box. So in your DAO you can change
suspend fun getUserList() : List<User>
to
suspend fun getUserList() : LiveData<List<User>>
Then in your repository adjust to
suspend fun getUserList(context: Context) : LiveData<List<User>> {
myDatabase = initializeDB(context)
userList = myDatabase!!.myDao().getUserList()
Log.d("MYLOG=", "DBREAD"+userList.value.size.toString())
return userList
}
and in the ViewModel
fun getUserFromDB(context: Context) = mainRepository.getUserList(context))
With these adjustments I think it should work.
Explaination
You used the liveData couroutines builder here
fun getUserFromDB(context: Context) = liveData(Dispatchers.IO) {
emit(mainRepository.getUserList(context))
}
As far as I understand this builder, it is meant to execute some asynchronous/suspend task and as soon as this task finishes the liveData you created will emit the result. That means that you only once receive the state of the user list an emidiately emit the list to the observer one single time and then this liveData is done. It does not observe changes to the list in the DB the whole time.
That is why it works perfectly for observing the API call (you want to wait until the call is finished and emit the response one single time), but not for observing the DB state(you want to observe the user list in the DB all the time and emit changes to the observer whenever the list is changed)
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!
Data get from the Sql server and get data json. this json data parsing retofit2.
Created Login Activity but its give error
MainActivity.kt
class MainActivity : AppCompatActivity() {
internal lateinit var api : APIInterface
private var compositeDisposable : CompositeDisposable? = null
var userName : String? = null
var password : String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnLogin.setOnClickListener {
userName = tvUsername.text.toString()
password = tvPassword.text.toString()
getUserName(userName!!,password!!)
}
}
fun getUserName(user : String, pass : String){
val retrofit = APIClient.apIClient
if (retrofit != null) {
api = retrofit.create(APIInterface::class.java)
}
compositeDisposable!!.add(api.getLoginData(user, pass)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.success.equals(1)){
val intent = Intent(this,Company::class.java)
startActivity(intent)
Toast.makeText(this,"Login Successfully!!!",Toast.LENGTH_LONG).show()
}else{
Toast.makeText(this,"UserName or Password is Wrong!!!",Toast.LENGTH_LONG).show()
}
},{
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
})
)
}
}
when Debbuger reached on compositeDisposable!!.add(api.getLoginData(user, pass) it's give Error kotlin.kotlinNullPointerException
RestApi Url :
http://localhost/Account/Login.php?user=ABC&pass=1
APIClient.kt
object APIClient {
val BASE_URL = "http://10.0.2.2/Account/"
var retrofit:Retrofit? = null
val apIClient:Retrofit?
get() {
if (retrofit == null)
{
retrofit = Retrofit.Builder().
baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
return retrofit
}
}
APIInterface.kt
interface APIInterface {
#GET("Login.php")
fun getLoginData(#Query("user") user : String,
#Query("pass") pass : String) : Observable<LoginList>
}
The most likely cause for the NullPointerException is that compositeDisposable is null.
At the beginning of MyActivity that variable is initialised to null and then it's never changed, so when you use the !! operator the exception is thrown.
I think you can initialise compositeDisposable directly with the correct value, i.e. something like val compositeDisposable = CompositeDisposable().
Also, val should be preferred over var whenever possible – as immutability is easier to control – and userName and password could probably be local variable or at least private
I'm trying to put a recyclerview which get its data from room. My function getAllHomework returns LiveData<List<Homework>>, but when I tried to set the return value to the recyclerview adapter, it will always return this error
Type Mismatch.
Required: List<Homework>
Found: List<Homework>?
Here's my HomeworkViewModel class which has the function getAllHomework looks like:
class HomeworkViewModel : ViewModel() {
private var matrixNumber: String? = null
private var schoolID: Int = 0
lateinit var listAllHomework: LiveData<List<Homework>>
lateinit var homeworkRepository: HomeworkRepository
fun init(params: Map<String, String>) {
schoolID = Integer.parseInt(params["schoolID"])
homeworkRepository = HomeworkRepository()
listAllHomework = homeworkRepository.getAllHomework(1, "2018")
}
fun getAllHomework(): LiveData<List<Homework>>{
return listAllHomework
}
}
And below is the part in my Homework activity that tries to set the value into recyclerview adapter but will always return the type mismatch error.
class Homework : AppCompatActivity(), LifecycleOwner {
lateinit var linearLayoutManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.homework)
linearLayoutManager = LinearLayoutManager(this)
rvHomeworks.layoutManager = linearLayoutManager
var adapter = AdapterHomework(this)
rvHomeworks.adapter = adapter
var homeworkViewModel = ViewModelProviders.of(this).get(HomeworkViewModel::class.java)
homeworkViewModel.init(params)
homeworkViewModel.getAllHomework().observe(this, Observer {
allHomework -> adapter.setHomeworkList(allHomework)
})
}
}
The line allHomework -> adapter.setHomeworkList(allHomework) above will show the Type Mismatch error I mentioned above.
Here's how my AdapterHomework looks like:
class AdapterHomework(context: Context): RecyclerView.Adapter<AdapterHomework.ViewHolder>() {
lateinit var homeworkList: List<Homework>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder{
val v = LayoutInflater.from(parent.context).inflate(R.layout.rv_homeworks, parent, false)
return ViewHolder(v);
}
#JvmName("functionToSetTheHomeworkList")
fun setHomeworkList(myHomeworkList: List<Homework>){
homeworkList = myHomeworkList
notifyDataSetChanged()
}
}
I could not find where in my code did I ever return List<Homework>? instead of List<Homework>.
This has actually nothing to do with Room, but with how LiveData was designed - specifically Observer class:
public interface Observer<T> {
void onChanged(#Nullable T t);
}
as you can see T (in your case List<Homework>) is marked as #Nullable therefore you will always get it's Kotlin equivalent List<Homework>? as a parameter of onChanged in your Observer implementation.
I would recommend changing setHomeworkList to something like this:
fun setHomeworkList(myHomeworkList: List<Homework>?){
if(myHomeworkList != null){
homeworkList = myHomeworkList
notifyDataSetChanged()
}
}
You can also use let function for that like this:
fun setHomeworkList(myHomeworkList: List<Homework>?){
myHomeworkList?.let {
homeworkList = it
notifyDataSetChanged()
}
}
What i want to achive is
Call IndexList API and display the responded list in a spinner -> Click on Spinner item i want to call 3 api in parallel using .zip operator and display in DetailViewModel
I am using flatmap for calling api in sequence
In first flatmap i am calling IndexList api and i am passing IndexList response to call 3 multiple api in parallel using Observable.zip operator but i am not able
to achive the second part of calling 3 API in parallel and display in view model
Code Snippet:
Spinner Click :
spinner_index_list?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
//Performing action onItemSelected and onNothing selected
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
mRecyclerView?.visibility = View.GONE
//Select item from spinner calling API
val symbolDefaultGroup = SymbolForDefaultGroup(indexExchangeSegmentList[pos], parent.getItemAtPosition(pos).toString())
grpIndexViewModel.getSearchSymbol(symbolDefaultGroup)?.observe(this#FragmentWatchlist, Observer { instrumentByIdResponse ->
mDataProvider = ExpandableDataProvider(instrumentByIdResponse)
mRecyclerView?.visibility = View.VISIBLE
if (instrumentByIdResponse != null)
initRecyclerView(savedInstanceState)
})
}
ViewModel :
fun getSearchSymbol(symbolForDefaultGroup: SymbolForDefaultGroup): LiveData<List<InstrumentByIdResponse>>? {
instrumentByIdResponse = null
instrumentByIdResponse = MutableLiveData<List<InstrumentByIdResponse>>()
instrumentByIdResponse = groupRepository.getSearchSymbol(symbolForDefaultGroup)
L.d("get symbol for default group method")
return instrumentByIdResponse
}
Repository:
override fun getSearchSymbol(symbolForDefaultGroup: SymbolForDefaultGroup): LiveData<List<InstrumentByIdResponse>> {
val mutableLiveData = MutableLiveData<List<InstrumentByIdResponse>>()
val instrumentsIdList = ArrayList<Instrument>()
val marketDataQuotesList = ArrayList<QuotesList>()
val subscriptionList = ArrayList<SubscriptionList>()
val symbolFroDefaultGroup: Observable<BaseResponse<SymbolForDefaultGroupResponse>> = remoteServices.requestSymbolForDefaultGroup(symbolForDefaultGroup)
symbolFroDefaultGroup
.flatMap { response ->
for (i in 0 until response.result!!.instruments.size) {
instrumentsIdList.add(Instrument(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
marketDataQuotesList.add(QuotesList(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
subscriptionList.add(SubscriptionList(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
}
val instrumentByIdResult = InstrumentById(instrumentsIdList)
val marketDataQuotes = MarketDataQuotes("ABC", "ABC", marketDataQuotesList, 1111)
val subscriptionList = Subscribe("ABC", "ABC", "Mobile", subscriptionList, 1111)
return#flatMap getDetails(instrumentByIdResult, marketDataQuotes, subscriptionList)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<List<InstrumentByIdResponse>>>() {
override fun onSuccess(t: BaseResponse<List<InstrumentByIdResponse>>) {
L.d("Success of Search Instrument")
mutableLiveData.value = transform1(t)
}
})
return mutableLiveData
}
private fun transform1(response: BaseResponse<List<InstrumentByIdResponse>>?): List<InstrumentByIdResponse>? {
return response!!.result!!.toList()
}
fun getDetails(instrumentById: InstrumentById, marketDataQuotes: MarketDataQuotes, subscription: Subscribe): Observable<DetailsModel> {
return Observable.zip(
remoteServices.requestInstrumentById(instrumentById),
remoteServices.requestMarketDataQuotes(marketDataQuotes),
remoteServices.requestSubscribe(subscription),
/*Observable.fromArray(remoteServices.requestInstrumentById(instrumentById)),
Observable.fromArray(remoteServices.requestMarketDataQuotes(marketDataQuotes)),
Observable.fromArray(remoteServices.requestSubscribe(subscription)),*/
Function3<List<InstrumentByIdResponse>, MarketDataQuotesResponse, SubscribeResult, DetailsModel>
{ instrumentByIdResponse, marketDataQuotesResponse, subscribeResponse ->
createDetailsModel(instrumentByIdResponse, marketDataQuotesResponse, subscribeResponse)
})
}
private fun createDetailsModel(instrumentByIdResponse: List<InstrumentByIdResponse>, marketDataQuotesResponse: MarketDataQuotesResponse, subscribeResult: SubscribeResult): DetailsModel {
return DetailsModel(instrumentByIdResponse, marketDataQuotesResponse, subscribeResult)
}
data class DetailsModel(
val instrument: List<InstrumentByIdResponse>,
val marketDataQuotes: MarketDataQuotesResponse,
val subscribe: SubscribeResult)
RemoteService :
fun requestInstrumentById(instrumentById: InstrumentById) = ApiService.requestInstrumentById(instrumentById)
APIService :
#Headers("Content-Type: application/json")
#POST("search/instrumentsbyid")
fun requestInstrumentById(#Body instrumentById: InstrumentById): Observable<BaseResponse<List<InstrumentByIdResponse>>>
API Structure
Error that i get on Zip Operator