Android Kotlin Retrofit MVVM return Null - android

I'm currently making a sample project about diagrams. I'm starting to use MVVM architecture recently, and I got stuck when the response is null. I also checked the Mutable Live Data to make sure that it is calling the API. Here's some of my code and the error-tag:
Model.kt
data class Model(
#SerializedName("FID") val FID: Int,
#SerializedName("region") val region: String,
#SerializedName("positive") val positive: Float
) {
}
ModelWrap.kt
data class ModelWrap(#SerializedName("samplesAPI") val attributes: Model){
}
ApiClient.kt
object ApiClient {
var retrofitService: ApiInterface? = null
const val BASE_URL = "https://sampleapi.../"
fun getApiSample() : ApiInterface {
if (retrofitService == null){
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofitService = retrofit.create(ApiInterface::class.java)
}
return retrofitService!!
}
}
ApiInterface.kt
interface ApiInterface {
#GET("samples")
fun getSampleData(): Call<List<ModelWrap>>
}
MainViewModel.kt
class MainViewModelconstructor(private val repository: ModelRepository) : ViewModel(){
val sampleList= MutableLiveData<List<ModelWrap>>()
val errorMessage = MutableLiveData<String>()
fun getSampleData(pieChart: PieChart){
val response = repository.getSampleData()
response.enqueue(object : Callback<List<ModelWrap>> {
override fun onResponse(
call: Call<List<ModelWrap>>,
response: Response<List<ModelWrap>>
) {
sampleList.postValue(response.body())
}
override fun onFailure(call: Call<List<ModelWrap>>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
MainViewModelFactory.kt
class MainViewModelFactoryconstructor(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MainViewModel::class.java)){
MainViewModel(this.repository) as T
} else {
throw IllegalArgumentException("Sample ViewModel Not Found")
}
}
}
MainRepository.kt
class MainRepository constructor(private val retrofitService: ApiInterface){
fun getSampleData() = retrofitService.getSampleData()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var pieChart: PieChart
lateinit var sampleViewModel: MainViewModel
private val sampleService = ApiClient.getApiSample()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pieChart = findViewById(R.id.PieChart)
sampleViewModel= ViewModelProvider(this, MainViewModelFactory(MainRepository(sampleService))).get(MainViewModel::class.java)
getPieChart(pieChart)
}
private fun getPieChart(pieCharts: PieChart) {
mainViewModel.mainList.observe(this, Observer {
Log.d("TAG sample" , "onCreate PieChart: $it")
Log.d("Tag Samples Response" , response.body().toString())
if (it != null) {
val sampleEntries: List<PieEntry> = ArrayList()
for ((attributes) in it!!) {
sampleEntries.toMutableList()
.add(PieEntry(attributes.positive, attributes.region))
//........................................................................
val description = Description()
description.text = "Samples Data"
pieChart.description = description
pieChart.invalidate()
}
}
})
mainViewModel.errorMessage.observe(this, Observer { })
mainViewModel.getSampleData(pieCharts)
}
}
and Lastly, here's some or log message:
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo#8b795c0 nm : com.example.diargram ic=null
D/Tag Sample Response: null
D/TAG Sample: onCreate PieChart: null
E/libc: Access denied finding property "ro.serialno"
V/StudioTransport: Agent command stream started.
V/StudioTransport: Transport agent connected to daemon.
I would appreciate it if someone can help me :D, Thank you

Finally, I found a solution for my problem:
I type the wrong endpoint inside the interface class and it should be like this:
interface ApiInterface {
#GET("sample")
fun getSampleData(): Call<List> }
When it comes to assigning the livedata to the view, based on my JSON I should call ArrayList instead of List
List item
Before :
val sampleEntries: List = ArrayList()
After :
val sampleEntries: ArrayList<PieEntry> = ArrayList()

Related

How to handle com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: For input string: "T1V 4Y8" Kotlin

what im trying to get is user list from https://randomuser.me/api/?results=50
using RetroFit , in mvvm pattren with live data
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val quoteService = RetrofitHelper.getInstance().create(QuoteService::class.java)
val repository = QuoteRepository(quoteService)
mainViewModel = ViewModelProvider(this,MainViewModelFactory(repository)).get(MainViewModel::class.java)
mainViewModel.quotes.observe(this) {
Log.d("hello", it.results.toString())
}
}
}
used method
interface QuoteService {
#GET("/api")
suspend fun getQuotes(#Query("results") results: Int):Response<QuoteList>
// baseUrl+/api/ + ?results=1
}
**Tried this method also same issue **
interface QuoteService {
#GET("/api/?results=50")
suspend fun getQuotes()):Response<QuoteList>
// baseUrl+/api/ + ?results=1
}
RetrofitHelper
object RetrofitHelper {
private val baseUrl="https://randomuser.me/"
fun getInstance() : Retrofit{
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Reposistory
class QuoteRepository(private val quoteService: QuoteService) {
private val quotesLiveData = MutableLiveData<QuoteList>()
val quotes:LiveData<QuoteList>
get() = quotesLiveData
suspend fun getQuotes(results:Int){
val result = quoteService.getQuotes(results)
if (result.body() != null){
quotesLiveData.postValue(result.body())
}
}
}
ViewModelIsLike
class MainViewModel(private val repository: QuoteRepository) :ViewModel() {
init {
viewModelScope.launch (Dispatchers.IO){
repository.getQuotes(20)**Here come my problem if i decrease the number or even i try to the normal way it occurs same problem **
}
}
val quotes:LiveData<QuoteList>
get() = repository.quotes
}
viewModelFactory
class MainViewModelFactory(private val repository: QuoteRepository):ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}
**Have to show list of user in recycler view **
I think the problem is while parsing the JSON, Somewhere you have declared some number field as String.
I hope your query is resolved.

How to maintain Singleton class for Web Socket Connection in Android Hilt-dagger?

I am new to Android Dagger-Hilt and I found it useful for my project. However, recently I want to use this concept to get my ServerConnection class to become Singleton across different view (fragment and activity). How can I achieve that?
I had tried to approach below but I can't get it Singleton as it will create 2 ServerConnection instance in my fragment/activity view. Where had I do wrong?
Current approach
AppModule.kt
#Singleton
#Provides
fun provideSocketConnection(tokenDao: TokenDao) : ServerConnection{
val token = runBlocking(Dispatchers.IO) { tokenDao.find() }
val tok = token!!.token
val refreshToken = token.refresh_token
return ServerConnection(URL)
}
ServerConnection.kt
class ServerConnection(url: String) {
enum class ConnectionStatus {
DISCONNECTED, CONNECTED
}
interface ServerListener {
fun onNewMessage(message: String?)
fun onStatusChange(status: ConnectionStatus?)
}
private var mWebSocket: WebSocket? = null
private val mClient: OkHttpClient
private val mServerUrl: String
private var mMessageHandler: Handler? = null
private var mStatusHandler: Handler? = null
private var mListener: ServerListener? = null
init {
mClient = OkHttpClient.Builder()
.readTimeout(3, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
mServerUrl = url
}
private inner class SocketListener : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
val m = mStatusHandler!!.obtainMessage(0, ConnectionStatus.CONNECTED)
mStatusHandler!!.sendMessage(m)
}
override fun onMessage(webSocket: WebSocket, text: String) {
val m = mMessageHandler!!.obtainMessage(0, text)
mMessageHandler!!.sendMessage(m)
}
override fun onClosed(
webSocket: WebSocket,
code: Int,
reason: String
) {
val m =
mStatusHandler!!.obtainMessage(0, ConnectionStatus.DISCONNECTED)
mStatusHandler!!.sendMessage(m)
}
override fun onFailure(
webSocket: WebSocket,
t: Throwable,
response: Response?
) {
disconnect()
}
}
fun connect(listener: ServerListener?) {
val request = Request.Builder()
.url(mServerUrl)
.build()
mWebSocket = mClient.newWebSocket(request, SocketListener())
mListener = listener
mMessageHandler =
Handler(Handler.Callback { msg: Message ->
mListener?.onNewMessage(msg.obj as String)
true
})
mStatusHandler = Handler(Handler.Callback { msg: Message ->
mListener!!.onStatusChange(msg.obj as ConnectionStatus)
true
})
}
fun disconnect() {
mWebSocket?.cancel()
mListener = null
mMessageHandler?.removeCallbacksAndMessages(null)
mStatusHandler?.removeCallbacksAndMessages(null)
}
fun sendMessage(message: String?) {
mWebSocket!!.send(message!!)
}
}
View (Fragment/Activity)
#AndroidEntryPoint
class RoomFragment : Fragment(), ServerConnection.ServerListener {
#Inject lateinit var socketConnection: ServerConnection
}
You need to annotate your AppModule.kt class with #InstallIn(SinggltonComponent::class).
To know more about the hilt component, check this detail, here.

Correct structure of implementing MVVM LiveData RxJava Dagger Databinding?

MainActivity
class MainActivity : AppCompatActivity() {
#Inject
lateinit var mainViewModelFactory: mainViewModelFactory
private lateinit var mainActivityBinding: ActivityMainBinding
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainActivityBinding = DataBindingUtil.setContentView(
this,
R.layout.activity_main
)
mainActivityBinding.rvmainRepos.adapter = mainAdapter
AndroidInjection.inject(this)
mainViewModel =
ViewModelProviders.of(
this#MainActivity,
mainViewModelFactory
)[mainViewModel::class.java]
mainActivityBinding.viewmodel = mainViewModel
mainActivityBinding.lifecycleOwner = this
mainViewModel.mainRepoReponse.observe(this, Observer<Response> {
repoList.clear()
it.success?.let { response ->
if (!response.isEmpty()) {
// mainViewModel.saveDataToDb(response)
// mainViewModel.createWorkerForClearingDb()
}
}
})
}
}
MainViewModelFactory
class MainViewModelFactory #Inject constructor(
val mainRepository: mainRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(mainViewModel::class.java) -> mainViewModel(
mainRepository = mainRepository
)
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
MainViewModel
class MainViewModel(
val mainRepository: mainRepository
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val mainRepoReponse = MutableLiveData<Response>()
val loadingProgress: MutableLiveData<Boolean> = MutableLiveData()
val _loadingProgress: LiveData<Boolean> = loadingProgress
val loadingFailed: MutableLiveData<Boolean> = MutableLiveData()
val _loadingFailed: LiveData<Boolean> = loadingFailed
var isConnected: Boolean = false
fun fetchmainRepos() {
if (isConnected) {
loadingProgress.value = true
compositeDisposable.add(
mainRepository.getmainRepos().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
run {
saveDataToDb(response)
)
}
},
{ error ->
processResponse(Response(AppConstants.Status.SUCCESS, null, error))
}
)
)
} else {
fetchFromLocal()
}
}
private fun saveDataToDb(response: List<mainRepo>) {
mainRepository.insertmainUsers(response)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
Log.d("Status", "Save Success")
}
override fun onError(e: Throwable) {
Log.d("Status", "error ${e.localizedMessage}")
}
})
}
}
MainRepository
interface MainRepository {
fun getmainRepos(): Single<List<mainRepo>>
fun getAllLocalRecords(): Single<List<mainRepo>>
fun insertmainUsers(repoList: List<mainRepo>): Completable
}
MainRepositoryImpl
class mainRepositoryImpl #Inject constructor(
val apiService: GitHubApi,
val mainDao: AppDao
) : MainRepository {
override fun getAllLocalRecords(): Single<List<mainRepo>> = mainDao.getAllRepos()
override fun insertmainUsers(repoList: List<mainRepo>) :Completable{
return mainDao.insertAllRepos(repoList)
}
override fun getmainRepos(): Single<List<mainRepo>> {
return apiService.getmainGits()
}
}
I'm quite confused with the implementation of MVVM with LiveData and Rxjava, in my MainViewModel I am calling the interface method and implementing it inside ViewModel, also on the response I'm saving the response to db. However, that is a private method, which won't be testable in unit testing in a proper way (because it's private). What is the best practice to call other methods on the completion of one method or i have to implement all the methods inside the implementation class which uses the interface.
Your ViewModel should not care how you are getting the data if you are trying to follow the clean architecture pattern. The logic for fetching the data from local or remote sources should be in the repository in the worst case where you can also save the response. In that case, since you have a contact for the methods, you can easily test them. Ideally, you could break it down even more - adding Usecases/Interactors.

How to test api requests and fill classes with fake data?

I can not find a solution to this problem on the Internet. Or is my code so bad?
Interface
interface GetWeatherService {
#GET("/data/2.5/forecast?")
fun getAllWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<ListWeather>
#GET("/data/2.5/weather?")
fun getCurrentWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<MainWeather>
#GET("/data/2.5/weather?")
fun getWeatherDataFromLocation(#Query("lat")lat: String, #Query("lon")lon: String, #Query("APPID") app_id: String): Call<DataFromLocation>
}
Client
object RetrofitClientInstance {
private var retrofit: Retrofit? = null
private var BASE_URL = "https://api.openweathermap.org/"
val retrofitInstance: Retrofit?
get(){
if(retrofit == null){
retrofit = retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
MainActivity()
class MainActivity: AppCompatActivity {
fun getDataFromApi(cityName: String) {
val service = RetrofitClientInstance.retrofitInstance!!.create(GetWeatherService::class.java)
// Service 1
service.getCurrentWeather(cityName, APP_ID).enqueue(object: Callback<MainWeather>{
override fun onFailure(call: Call<MainWeather>, t: Throwable) {
}
override fun onResponse(call: Call<MainWeather>, response: Response<MainWeather>) {
val weather = response.body()
val currentWeather = CurrentWeather(
weather!!.main.temp,
weather.weather[0].description,
weather.name,
weather.weather[0].main
)
updateCurrentWeatherUI(currentWeather)
}
})
service.getAllWeather(cityName, APP_ID).enqueue(object: Callback<ListWeather>{
override fun onFailure(call: Call<ListWeather>, t: Throwable) {
}
override fun onResponse(call: Call<ListWeather>, response: Response<ListWeather>) {
val weather = response.body()!!.list
for(item in weather){
val weatherList = NextFiveDayWeather(
item.dt,
item.main.temp,
item.weather[0].description
)
weatherArray.add(weatherList)
updateUI(weatherArray)
}
}
})
}
}
Data class
data class MainWeather (
var dt: Long,
var main: MainDTO,
var weather: List<WeatherDTO>,
var dt_txt: String,
var name: String
)
data class WeatherDTO (var main: String, var description: String, var icon: String)
data class ListWeather (#SerializedName("list") var list: List<MainWeather>)
I can’t test the request to api and I can’t fill the classes with fake data?
Tell me please, what should I do?
What should I study? And if you are not difficult can give advice for the future?
You can download the software Postman to test api endpoints: https://www.getpostman.com/

Send a parameter to Kotlin retrofit call

I am a beginner in Kotlin. I need to send a variable parameter from my Activity to a Retrofit call.
This is my call in on Create of Detail Activity
override fun onCreate(savedInstanceState: Bundle?) {
//...
val id = intent.getStringExtra("id")
// Get the ViewMode
val mModel = ViewModelProviders.of(this).get(myObjectViewModel::class.java)
//Create the observer which updates the UI.
val myObjectByIdObserver = Observer<MyObject> { myObject->
//...
}
//Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getObjectById.observe(this, myObjectByIdObserver)
}
Here I insert value hardcode, I need the parameter received from the previous Activity.
class MyObjectViewModel : ViewModel() {
//this is the data that we will fetch asynchronously
var myObject: MutableLiveData<MyObject>? = null
val getMyObjectById: LiveData<MyObject>
get() {
if (myObject == null) {
myObject = MutableLiveData()
loadMyObjectById()
}
return myObject as MutableLiveData<MyObject>
}
private fun loadMyObjectById() {
val retrofit = Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
val call = api.myObjectById(100)
call.enqueue(object : Callback<MyObject> {
override fun onResponse(call: Call<MyObject>, response: Response<MyObject>) {
myObject!!.value = response.body()
}
override fun onFailure(call: Call<MyObject>, t: Throwable) {
var tt = t
}
})
}
My API:
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?): Call<MyObject>
}
You can do this by ``#Query``` annotation.
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?, #Query("a_param") aParam: String?): Call<MyObject>
}
Edited. I completely misread your intension.
What you need seems to be ViewModelProvider.NewInstanceFactory like
class MyObjectViewModel(val id: Int): ViewModel() {
class Factory(val id: Int) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyObjectViewModel(id) as T
}
}
}
then
val myViewModel = ViewModelProviders
.of(this, MyObjectViewModel.Factory(id))
.get(MyObjectViewModel::class.java)

Categories

Resources