Passing Variable to Interface - android

Trying to pass the device ID to the interface and I am not getting anywhere. I essentially want to store the device ID to a variable and put it in the #Get for the interface.
I tried using #Path but I am not familiar enough with it to use it.
The url needs to look like this on the call
http://xxx.xx.xxx.xxx/apps/api/109/devices/65?access_token=xxxxxx
Interface
interface DeviceDetailsAPIClient {
#GET("devices/<item id here>")
fun getDevicesDetailsAsync(#Query("access_token") access_token: String): Deferred<Response<DeviceDetails>>
}
MainActivity2 where the ID is passed to
class MainActivity2 : AppCompatActivity() {
private val tag : String = MainActivity2::class.java.simpleName
var deviceID: String = intent.getStringExtra("deviceID")
private lateinit var adapterDetails: DeviceDetailsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rv_devices.layoutManager = LinearLayoutManager(this)
rv_devices.hasFixedSize()
adapterDetails = DeviceDetailsAdapter(listOf()) { deviceDetails: DeviceDetails -> deviceDetails }
rv_devices.adapter = adapterDetails
loadDeviceDetails()
}
private fun loadDeviceDetails() {
GlobalScope.launch(Dispatchers.Main) {
try {
val webResponseDetails = deviceDetailsApi.getDevicesDetailsAsync(access_token = "xxxxxx").await()
if (webResponseDetails.isSuccessful) {
val deviceDetails: DeviceDetails? = webResponseDetails.body()
Log.d(tag, deviceDetails?.toString())
//adapterDetails.deviceDetails = deviceDetails ?: listOf()
adapterDetails.notifyDataSetChanged()
} else {
Log.e(tag, "Error ${webResponseDetails.code()}")
Toast.makeText(this#MainActivity2, "Error ${webResponseDetails.code()}", Toast.LENGTH_LONG).show()
}
} catch (e: IOException) {
Log.e(tag, "Exception " + e.printStackTrace())
Toast.makeText(this#MainActivity2, "Exception ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
This is the var I am storing the ID in
var deviceID: String = intent.getStringExtra("deviceID")
Now how do I get that to the interface?

The #Path annotation can be used to change the URL:
interface DeviceDetailsAPIClient {
#GET("devices/{deviceId}")
fun getDevicesDetailsAsync(
#Path("deviceId") deviceId: Long,
#Query("access_token") access_token: String
): Deferred<Response<DeviceDetails>>
}

Related

Unable to invoke no-args constructor for retrofit2.Call MVVM Coroutines Retrofit

I want to use coroutines in my project only when I use coroutines I get the error :Unable to invoke no-args constructor. I don't know why it's given this error. I am also new to coroutines.
here is my apiclient class:
class ApiClient {
val retro = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Here is my endpoint class:
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.limit,
#Query("radius") radius: String = Constants.radius,
#Query("client_id") id: String = Constants.clientId,
#Query("client_secret") secret: String = Constants.clientSecret,
#Query("v") date: String
): Call<VenuesMainResponse>
my Repository class:
class VenuesRepository() {
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
api.get(
city = city,
date = date
).enqueue(object : Callback<VenuesMainResponse>{
override fun onResponse(call: Call<VenuesMainResponse>, response: Response<VenuesMainResponse>) {
val res = response.body()
if (response.code() == 200 && res != null) {
_data.value = res
} else {
_data.value = null
}
}
override fun onFailure(call: Call<VenuesMainResponse>, t: Throwable) {
_data.value = null
}
})
}
}
my ViewModel class:
class VenueViewModel( ) : ViewModel() {
private val repository = VenuesRepository()
fun getData(city: String, date: String): LiveData<VenuesMainResponse?> {
viewModelScope.launch {
try {
repository.fetch(city, date)
} catch (e: Exception) {
Log.d("Hallo", "Exception: " + e.message)
}
}
return repository.data
}
}
part of activity class:
class MainActivity : AppCompatActivity(){
private lateinit var venuesViewModel: VenueViewModel
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
venuesViewModel = ViewModelProvider(this)[VenueViewModel::class.java]
venuesViewModel.getData(
city = "",
date = ""
).observe(this, Observer {
it?.let { res ->
initAdapter()
rv_home.visibility = View.VISIBLE
adapter.setData(it.response.venues)
searchData = it.response.venues
println(it.response.venues)
}
})
this is my VenuesMainResponse data class
data class VenuesMainResponse(
val response: VenuesResponse
)
I think the no-args constructor warning should be related to your VenuesMainResponse, is it a data class? You should add the code for it as well and the complete Log details
Also, with Coroutines you should the change return value of the get() from Call<VenuesMainResponse> to VenuesMainResponse. You can then use a try-catch block to get the value instead of using enqueue on the Call.
Check this answer for knowing about it and feel free to ask if this doesn't solve the issue yet :)
UPDATE
Ok so I just noticed that it seems that you are trying to use the foursquare API. I recently helped out someone on StackOverFlow with the foursquare API so I kinda recognize those Query parameters and the Venue response in the code you provided above.
I guided the person on how to fetch the Venues from the Response using the MVVM architecture as well. You can find the complete code for getting the response after the UPDATE block in the answer here.
This answer by me has code with detailed explanation for ViewModel, Repository, MainActivity, and all the Model classes that you will need for fetching Venues from the foursquare API.
Let me know if you are unable to understand it, I'll help you out! :)
RE: UPDATE
So here is the change that will allow you to use this code with Coroutines as well.
Repository.kt
class Repository {
private val _data: MutableLiveData<mainResponse?> = MutableLiveData(null)
val data: LiveData<mainResponse?> get() = _data
suspend fun fetch(longlat: String, date: String) {
val retrofit = Retro()
val api = retrofit.retro.create(api::class.java)
try {
val response = api.get(
longLat = longlat,
date = date
)
_data.value = response
} catch (e: Exception) {
_data.value = null
}
}
}
ViewModel.kt
class ViewModel : ViewModel() {
private val repository = Repository()
val data: LiveData<mainResponse?> = repository.data
fun getData(longLat: String, date: String) {
viewModelScope.launch {
repository.fetch(longLat, date)
}
}
}
api.kt
interface api {
#GET("v2/venues/search")
suspend fun get(
#Query("ll") longLat: String,
#Query("client_id") id: String = Const.clientId,
#Query("client_secret") secret: String = Const.clientSecret,
#Query("v") date: String
): mainResponse
}
MainActivity.kt
private val viewModel by viewModels<ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.getData(
longLat = "40.7,-74",
date = "20210718" // date format is: YYYYMMDD
)
viewModel.data
.observe(this, Observer {
it?.let { res ->
res.response.venues.forEach { venue ->
val name = venue.name
Log.d("name ",name)
}
}
})
}
}

How to return response value from coroutine

I've recently work with Kotlin, and got really stuck with this one problem. I'm trying to return float value receive onResponse of a coroutine api call function. I'm trying to create a class that handle api call and use it on a fragment.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
....
func getBalance(cardNo: String): Float?{
val cardBalance: Float = null
GlobalScope.launch(Dispatchers.Main) {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
cardBalance = card.cardAvailableBalance
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
context!!.toast("Errror: " + message)
promptErrorDialog(error)
}
}
})
}}
return cardBalance
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
val galA = 10.5f
val galB = "Test"
private var pass = FunctionA(context!!, valA ,valB)
....
val point = "sasd12125"
private fun gooToo(){
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
....
}
What happend right now, since the coroutine will take some time in background, val B are null and didn't get the value obtained onResponse. Only after I try to call that functionA again, then the value are updated. I'm not sure if I'm doing it right and I've tried to search for solutions, but it doesn't suit with my current situation. Probably my searching skill are soo bad.
Output
TEST
value B: null
How should I wait for the coroutine to finish before return the cardBalance value?
Proper way to return a single value from a coroutine is to use await().
Now, since you use coroutine to wrap some callback API, that wouldn't work so well. So I would suggest to go with something like this:
val scope = CoroutineScope(Dispatchers.IO)
suspend fun getBalance(cardNo: String): Float{
val res = CompletableDeferred<Float>()
scope.launch {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
res.complete(card.cardAvailableBalance)
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
res.completeExceptionally(message)
withContext(Dispatchers.Main) {
promptErrorDialog(error)
}
}
}
})
}
return res.await()
}
A few points to consider. First, I used Dispatchers.IO instead of Dispatchers.Main, and switch to Main thread only when needed using withContext(Dispatchers.Main). Otherwise, you're just running your IO on the main thread, coroutine or not.
Second, using GlobalScope is a bad practice, and you should avoid it at all cost. Instead I create a custom scope that you can .cancel() to prevent coroutine leak.
Third, the most correct way would be to return Deferred<Float>, and not Float, since await() is blocking. But I left it for simplicity.
To solve my little problem I end up using a callback to pass the response data. I found that this method work perfectly and easier to understand for my level of understanding. This method skeleton can also be reused for any api service call that I want to use in the future.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
private var card: Card? = null
interface CardBalanceCallback {
fun processFinish(output: Boolean, cardBalance: Float?)
}
fun getCardBalance(cardNo: String, callback: CardBalanceCallback) = runBlocking {
getBalance(cardNo, callback)
}
private fun getBalance(cardNo: String, callback: CardBalanceCallback) = CoroutineScope(Dispatchers.Main).launch {
try {
val response = cardApi.getCardBalance(cardNo).await()
if (response.isSuccessful) {
card = response.body()
callback.processFinish(true, card!!.cardAvailableBalance)
} else {
callback.processFinish(false, null)
val error: ApiError = ErrorUtils.parseError(response)
val message = when {
error.error.code.isNotEmpty() -> error.error.code + error.error.message
else -> error.code + error.message
}
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
promptErrorDialog(error)
}
} catch (e: HttpException) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast(e.message.toString())
} catch (e: Throwable) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast( e.message.toString())
}
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
private var funcService = FunctionA(null, null ,null)
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity!!.application as App).component.inject(this)
val valA = 10.5f
val valB = "Test"
val cardNo = "4001526976443264"
val cardExpDate = "1119"
funcService = FunctionA(context!!, valA ,valB)
getCardBalanceApi(cardNo, cardExpDate)
}
....
private fun getCardBalanceApi(cardNo: String, cardExpDate: String?) {
showLoadingDialog()
funcService.getCardBalance(cardNo, object : SmartPayService.CardBalanceCallback {
override fun processFinish(output: Boolean, cardBalance: Float?) {
dismissLoadingDialog()
if (cardBalance != null) {
checkBalance(cardNo, cardBalance, cardExpDate)
}
}
})
}
....
}
This is some simple changes that I made for this particular problem on my first post. This approach might not be as good or smooth enough as I'm still learning. Hope it help some of you guys. cheers
Make getBalance() a suspend function and then call using lifecycleScope in your fragment
private fun gooToo(){
lifecycleScope.launch {
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
}
getBalance() function signature would be something like
suspend fun getBalance(): Float = withContext(Dispatchers.IO)

Getting Null exception in Toast inside fragment

Here's the code where i am getting exception, tried various ways to implement context but nothing is working out.
class GrowthStoryFragment : Fragment() {
private val TAG = "GrowthStoryFragment"
companion object{
private var countryID = "1"
private var date = "MAT TY"
private var spec = "val"
private var businessUnitID = "2"
private var category = "Fresh Milk"
private var firstReportTypeId = "1" //fixed for growth story and share story
private var isGroup = "false" //fixed to false
}
private val backendApi = WinRetrofitHelper.winApiInstance()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_growth_story, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getSpinnerResponse(businessUnitID, isGroup,firstReportTypeId)
getSuperRegionName(countryID, date,spec," ",businessUnitID, category, firstReportTypeId, isGroup)
growth_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val item = parent?.getItemAtPosition(position) as RespTwo
category = item.nameValue
Log.e(TAG,"Category name is: " + category)
getSuperRegionName(countryID, date,spec," ",businessUnitID, category, firstReportTypeId, isGroup)
}
}
}
private fun getSpinnerResponse(businessUnitID: String, isGroup: String, firstReportTypeId: String){
val request = backendApi.getCategoryBusinessUnit(businessUnitID, isGroup, firstReportTypeId)
request.enqueue(object : Callback<List<RespTwo>> {
override fun onFailure(call: Call<List<RespTwo>>?, t: Throwable?) {
Log.e(TAG, "Failure Super Region Name: ")
}
override fun onResponse(call: Call<List<RespTwo>>?, response: Response<List<RespTwo>>?) {
val myresponse = response?.body()
if (response?.body() != null && !response?.body()!!.isEmpty()) {
growth_spinner.adapter = GrowthSpinnerAdapter(response?.body())
Log.e(TAG, "Super Region Name: " + myresponse?.get(0)?.nameValue)
} else {
Toast.makeText(context!!.applicationContext, "Data Not Available!", Toast.LENGTH_LONG).show()
}
}
})
}
private fun getSuperRegionName(countryID: String, date: String, spec: String, superMarket: String,businessUnitID: String, category: String, firstReportTypeId: String, isGroup: String) {
val request = backendApi.getSuperRegion(countryID)
request.enqueue(object : Callback<List<RespMy>> {
override fun onFailure(call: Call<List<RespMy>>?, t: Throwable?) {
Log.e(TAG, "Failure Super Region Name: ")
}
override fun onResponse(call: Call<List<RespMy>>?, response: Response<List<RespMy>>?) {
val myresponse = response?.body()
if (response?.body() != null && !response?.body()!!.isEmpty()) {
getDataFromApi(countryID, date, spec, myresponse?.get(0)!!.nameValue, businessUnitID, category, firstReportTypeId, isGroup)
Log.e(TAG, "Super Region Name: " +countryID+" "+ date+" "+ spec+" "+ myresponse?.get(0)?.nameValue+" "+businessUnitID+" "+ category+" "+ firstReportTypeId+" " + isGroup+" ")
} else {
Toast.makeText(myApplicationContext, "Data Not Available!", Toast.LENGTH_LONG).show()
}
}
})
}
}
Please suggest something and i have heard a viewModel conversion of the requests made directly from fragments can fix this out but i don't know how to do that. Please help in either way.
Update:
Don't want to use static context here
ViewModel and Repository pattern is way to go.
You are doing async network call on Main thread, very bad idea.
For learning purposes - here is how to get your code working:
replace:
myApplicationContext = context!!.applicationContext
with:
myApplicationContext = requireContext()
or better - get rid of this variable entirely and just use requireContext() instead.
Toasts are usually applied to the activity rather than an generic context, and demanding the context via context!! should be avoided; it is optional for a reason. I would try the following after defining #string/data_not_available and #string/super_region_name_template in the string resource xml:
val body = response?.body()
if (body?.isNotEmpty() == true) {
growth_spinner.adapter = GrowthSpinnerAdapter(body)
Log.e(TAG, getString(R.string.super_region_name_template, body.get(0).nameValue)) // if applicable, use .first() over .get(0)
} else {
activity?.run {
Toast.makeText(this, getString(R.string.data_not_available), Toast.LENGTH_LONG).show()
}
}
try to use this
myApplicationContext: Context = this.context ?: return
then
Toast.makeText(myApplicationContext, "Data Not Available!",Toast.LENGTH_LONG).show()
for more details, you can see this link

Kotlin MainThread

I'm new in Kotlin.
I have a problem, with "The application may be doing too much work on its main thread".
I have 2 different activity. The principal I use for login, the second for send a get http and receive a json object.
Where is my problem? I wrong to use 2 different activity o the problem is asyncdo? I am in deep sea.
class GitActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.git_activity)
Recycle_view.layoutManager = LinearLayoutManager(this)
Recycle_view.adapter = MainAdapter()
var url = intent.getStringExtra("URL")
doAsync {
fetchJson(url)
uiThread { longToast("Request performed") }
}
}
fun fetchJson(url: String) : List<NameFileList> {
var request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
print("sono qui")
print(url)
val body = response?.body()?.string()
print(body)
}
override fun onFailure(call: Call, e: IOException) {
print("Error Failure")
}
})
}
and my Main Class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SearchButton.setOnClickListener {
verifyNetwork()
}
}
private fun getUsername(): String {
val username = Username_editText.text.toString()
Log.d("MainActivity", "UserName is + $username")
return username
}
private fun getRepositoryName(): String {
val nameRepository = RepositoryName_editText.text.toString()
Log.d("MainActivity", "UserName is + $nameRepository")
return nameRepository
}
private fun verifyNetwork()
{
if(isConnected(this)){
val url = CreateUrl().createUrl(getUsername(), getRepositoryName())
print(url)
var intent = Intent(this, GitActivity::class.java)
intent.putExtra("URL", url)
startActivity(intent)
}
else {
//POPUP DI ERRORE NETWORK
}
}
private fun isConnected(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetworkInfo
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
}
}
Are your icons / resources appropriately sized for a mobile application? If your assets are too large, inflation could take over the main thread.
You'd better to use Retrofit + Rxjava for handling Network call.
Retrofit :
https://square.github.io/retrofit/
RxJava :
https://github.com/ReactiveX/RxJava
You can also refer this link for check example: How to get the response URL from Retrofit?

Trouble with "For-Loop range must have an 'iterator()' method" and JSON issues

I am teaching myself Kotlin and android dev. So, i'm sure most of my issue is lack of knowledge, but i've been hung up on this part for day or two. I think my issue is partly my JSON query, and mostly my rookieness.
In my for loop below, I'm getting the following error from the IDE "For-Loop range must have an 'iterator()' method". This is in regards to 'cycloneList' in: for(stormInfo in cycloneList)
I've linked my "dummy" JSON data I'm using can be found here: https://api.myjson.com/bins/19uurt to save a bit of space here in the question.
Problem code
`var cycloneList = response?.body()?.currenthurricane?.stormInfo?.get(0)
if (cycloneList != null) {
for (stormInfo in cycloneList) { <<--Problem
val newCyclone = "Name: ${cycloneList.stormName}"
cycloneStrings.add(newCyclone)
}
}`
FULL CODE
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//creates a new CycloneRetriever object from CycloneHelp.kt
var retriever = CycloneRetriever()
val callback = object : Callback<Cyclones> {
override fun onResponse(call: Call<Cyclones>?, response: Response<Cyclones>?) {
println("Got a response!")
var cycloneList = response?.body()?.currenthurricane?.stormInfo?.get(0)
var cycloneStrings = mutableListOf<String>()
if (cycloneList != null) {
for (stormInfo in cycloneList) { //TODO Figure this out!!
val newCyclone = "Name: ${cycloneList.stormName}"
cycloneStrings.add(newCyclone)
}
}
var layoutMovieListView = movieListView
var movieListAdapter = ArrayAdapter(this#MainActivity, android.R.layout.simple_list_item_1,
cycloneStrings)
layoutMovieListView.adapter = movieListAdapter
}
override fun onFailure(call: Call<Cyclones>?, t: Throwable?) {
println("The thing, it failed!")
}
}
retriever.getCyclones(callback)
}
I'm using Retrofit to access/handle JSON data
Retrofit JSON code
interface WeatherWunderGroundAPI {
#GET("bins/19uurt")
fun getCyclones() : Call<Cyclones>
}
class Cyclones(val currenthurricane: CurrentHurricane)
class CurrentHurricane(val stormInfo: List<StormInfo>)
class StormInfo(val stormName: String)
class CycloneRetriever {
val service : WeatherWunderGroundAPI
init {
val = retrofitCyclone
Retrofit.Builder().baseUrl("https://api.myjson.com/")
.addConverterFactory(GsonConverterFactory.create()).build()
service = retrofitCyclone.create(WeatherWunderGroundAPI::class.java)
}
fun getCyclones(callback: Callback<Cyclones>) {
val call = service.getCyclones()
call.enqueue(callback)
}
}
Your stormInfo is a List which provides the mandatory iterator() and this is what you want to iterate over:
var cycloneList = response?.body()?.currenthurricane?.stormInfo ?: emptyList()
for (stormInfo in cycloneList) { }
Hope it helps...

Categories

Resources