Ktor with Spring boot WebFlux web api - android

I have a simple Spring Boot WebFlux api and I am trying to use Ktor to call it.
Here is my controller
#RestController
#RequestMapping("api/v1")
class TestController {
#RequestMapping(method = [RequestMethod.GET], value = ["/values"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun sayHello(#RequestParam(value = "name") name:String): Flux<Example> {
return Flux.interval(Duration.ofSeconds(1))
.map { Example(number = generateNumber()) }
.share()
}
fun generateNumber(): Int{
return ThreadLocalRandom.current().nextInt(100)
}
}
It just returns an object with a number every second
Now on the client side is where I want to use Ktor to get the data but I am unsure what to do. Reading the docs it looks like Scoped streaming is what I need but I am not sure how to get it to work with my controller
Here is the client code I have so far.
suspend fun getData(): Flow<Example> {
return flow {
val client = HttpClient()
client.get<HttpStatement>("http://192.168.7.24:8080/api/v1/values?name=hello").execute{
val example = it.receive<Example>()
emit(example)
}
}
}
When I try to make the call on the Android client I get this error
NoTransformationFoundException: No transformation found: class
io.ktor.utils.io.ByteBufferChannel (Kotlin reflection is not
available)
So it looks like I cant just serialize the stream into my object so how do I serialize ByteReadChannel to an object?
This is my first time trying out spring and ktor

To be able to serialize the data into an object you need to do t manually by reading the data into a byte array
client.get<HttpStatement>("http://192.168.7.24:8080/api/v1/values?name=hello").execute{
val channel = it.receive<ByteReadChannel>()
val byteArray = ByteArray(it.content.availableForRead)
channel.readAvailable(byteArray,0,byteArray.size)
val example:Example = Json.parse<Example>(stringFromUtf8Bytes(byteArray))
}

You can install a plugin for JSON serialization/deserialization, which will allow you to use data classes as generic parameters
https://ktor.io/docs/json.html#jackson

Related

Insert list of class's object to Room by parsing API data using MVVM in Kotlin

I built an application in Kotlin using MVVM. I fetched the API response from the server successfully. Now I want to insert API's parsing data into RoomDB.
API response included both JSON Object & Array. I want to insert specific data from API to DB. Please help me with how I can make an entity class and set parsing data to the class data members with/without loops and insert it into RoomDB by making a single list of the whole class.
Please provide tutorial links or any kind of material links instead of the Android Developers Guide. Thanks a lot!
In API Response we have many data but actually, we don't need that all that so basically we to create one data class that is only constant the specific that actually, we need. and that all operation is performed in a repository and we manage it.
entity class that only contains essential data
#Entity(tableName = "wallpaper")
data class WallpaperDataClass (
#PrimaryKey(autoGenerate = true)
val note_id:Int=0,
val photoId: Int,
val photos_url: String,
val photographer_name: String,
val photographer_url: String,
val src:String
)
Fill the data in model
if (NetworkUtils.isOnline(applicationContext)) {
/**
* Online
* if Your net is online then call api
*/
try {
val result: Response<PhotoModel> =
wallpaperInterface.getWallpaper(authorization, page, per_page)
if (result.body() != null) {
val photos = mutableListOf<WallpaperDataClass>()
result.body()!!.photos.forEach {
// in blows line we set data in modal
val wallpaperDataClass = WallpaperDataClass(
photoId = it.id,
photos_url = it.url,
photographer_name = it.photographer,
photographer_url = it.photographerUrl,
src = it.src.portrait
)
photos.add(wallpaperDataClass)
if (!offlineDatabase.getDao().exists(it.id)){
offlineDatabase.getDao().insertWallpaper(wallpaperDataClass)
}
mutableLiveData.postValue(ErrorHandling.Success(photos))
}
} else {
Log.d("WallpaperResponse", "getWallpaper: ${result.message()}")
}
} catch (e: Exception) {
mutableLiveData.postValue(ErrorHandling.Faild(e.localizedMessage!!.toString()))
}
} else {
/**
* Offline
*if Your net is offline then fetch from db
*/
try {
val wallpaper = offlineDatabase.getDao().getOfflineWallpaper()
mutableLiveData.postValue(ErrorHandling.Success(wallpaper))
} catch (e: Exception) {
mutableLiveData.postValue(ErrorHandling.Faild(e.localizedMessage!!.toString()))
}
}
}
}
Video Tutorial

Serializer for xml data while calling http post method (Ktor lib)

As I am newbie in Android, I am building an application in which I need to update layer data on Geo server. For that scenario, I m calling post method along with XML body request.
I need to send data in xml format in request body and need to get response in xml. For that, I tried using XmlSerializer instead of JsonFeature but I got error saying "XmlSerializer is not comapanion object, need to initialize here"
val httpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json.nonstrict)
}
}
Thanks in Advance!!
There are no XML related features, but you can use existing Json with the XML serializer
The first thing you need to do is to find an appropriate serializer. I used https://github.com/pdvrieze/xmlutil.
implementation("net.devrieze:xmlutil-android:0.20.0.1")
You also can use other platform dependencies(I used the JVM and multiplatform).
Next you can configure the JsonFeature feature with a custom serializer:
val client = HttpClient {
Json {
serializer = XMLSerializer()
accept(ContentType.Application.Xml)
}
}
Using ContentType.Application.Xml
And finally, add the serializer:
#OptIn(ImplicitReflectionSerializer::class)
class XMLSerializer(private val format: XML = XML()) : JsonSerializer {
override fun read(type: TypeInfo, body: Input): Any {
val text = body.readText()
val deserializationStrategy = format.context.getContextual(type.type)
val mapper = deserializationStrategy
?: type.kotlinType?.let { serializer(it) }
?: type.type.serializer()
return format.parse(mapper, text) ?: error("Failed to parse response of type $type. The result is null.")
}
override fun write(data: Any, contentType: ContentType): OutgoingContent {
val serializer = data::class.serializer() as KSerializer<Any>
val text = format.stringify(serializer, data, null)
return TextContent(text, contentType)
}
}
Here is the full result sample with the server(adopted to run without Android): https://gist.github.com/e5l/3b4d5d704b4d7c6e2a65cf68de8e9ca4

Need help Kotlin Coroutines, Architecture Component and Retrofit

I'm trying to wrap my head around the mentioned components and I can't get it right. I want to do something very simple: Fetch data from the network and present it to the user. Currently am not yet caching it as am still learning new Coroutine features in Architecture components. Every time app loads I get an empty model posted, which seems weird.
My API is get hit fine and response is 200 which is OK.
Below is what I have attempted:
POJO
data class Profile(#SerializedName("fullname") val fullName : String.....)
Repository
class UserRepo(val context: Context, val api: Api) {
suspend fun getProfile(): Profile
{
val accessToken = ....
return api.getUserProfile(accessToken)
}
}
API
interface GatewayApi {
#GET("users/profile")
suspend fun getUserProfile(#Query("access-token") accessToken: String?): Profile
}
ViewModel
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val usersRepo = UserRepo(application.applicationContext, Apifactory.Api)
val userProfileData = liveData{
emit(usersRepo.getProfile())
}
fun getProfile() = viewModelScope.launch {
usersRepo.getProfile()
}
}
Finally my fragment's relevant code
val viewModel = ViewModelProviders.of(activity!!).get(UserViewModel::class.java)
viewModel.userProfileData.observe(this, Observer<UserProfile> {
//it is having nulls
})
//trigger change
viewModel.getProfile()
So I added HTTP requests and responses (thanks to #CommonsWare for pointing that out) and it happened I had used a different model than I was supposed to use. The correct model that mapped the JSON response was ProfileResponse and as you can see in my posted code, I used Profile instead. So all fields were empty as Gson could not correctly serialize JSON into Profile object.
All the credit goes to #CommonsWare for pointing that out in comment.

How to unit test retrofit call?

For Example I have a retrofit interface such as:
interface SampleService {
fun getSomething(#body someBody: SomeBody)
}
Now I have a class which uses this interface such as:
class UserRequester(val service: SampleService) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(SomeBody(someValue))
// ...
}
}
I want to test this class but dont know how to mock it.
I'm trying the following:
val mockSampleService = mock()
val userRequester = UserRequester(mockSampleService)
val requestBody = SomeBody(someString))
when(mockSampleService.getSomething(requestBody)).return(myExpectedValue)
....
My problem is that since I create the request object inside the function, I could not make the mock when().thenReturn() to work since i am technically passing two different object.
How should I test this? Thanks in advance.
The mocking problem (UserRequester)
You are not able to mock the mockSampleService method because your class is creating the SomeBody object and is different from the SomeBody object you are creating in your test.
Now you have 2 options:
Use Mockito.any() in your test, in this way you basically say that whatever your method is gonna use as parameter you will return the mocked behaviour
Use a factory that given a someString returns you a SomeObject like this:
// the factory
class SomeObjectFactory{
fun createSomeObject(someString: String): SomeObject {
return SomeObject(someString)
}
}
//the class
class UserRequester(
val service: SampleService, val factory: SomeObjectFactory
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(factory.createSomeObject(someValue))
// ...
}
}
//the test
class MyTest{
#Test
fun myTestMethod(){
val mockSampleService = mock()
val factory = mock()
val someBody = mock()
val userRequester = UserRequester(mockSampleService, factory)
`when`(factory.createSomeObject(someString)).thenReturn(someBody)
`when`(mockSampleService.getSomething(someBody)).thenReturn(myExpectedValue)
//rest of the code
}
}
The second approach is the cleanest one.
Testing Retrofit calls (SampleService)
I wouldn't unit test a Retrofit call.
When you are dealing with frameworks, apis, databases, shared preferences is always preferable to do integration tests instead of unit tests.
In this way you are actually testing that your code is working with the outside world.
I suggest you to test Retrofit calls with MockWebServer (it's a library from Square, the same company that developed OkHttp and Retrofit).
This read may be also helpful.
Probably SomeBody is a plain value object, since Retrofit requests work with value objects. If you define the equals method for the SomeBody class then the eq matcher will work, and you can write using mockito-kotlin:
whenever(mockService.getSomething(eq(SomeBody(someString)))).thenReturn(stubbedResult)
Actually, you can omit the eq matcher, Mockito will use the equals method for matching.
If SomeBody is a Kotlin data class then the equals method is automatically defined by comparing the fields.
If for some reason you don't want to rely on equals, then you can use the argThat matcher defined in mockito-kotlin:
whenever(mockService.getSomething(argThat { theField == someValue })).thenReturn(stubbedResult)
The problem is that there is static dependency on SomeBody's constructor:
val response = service.getSomething(SomeBody(someValue))
What you could do to have control over the instantiation of SomeBody is to use a "provider" or "factory" object, you can inject it in the constructor and invoke it at the right time:
interface SampleService {
fun getSomething(someBody: SomeBody)
}
open class SomeBody(val body: String)
open class UserRequester(
val service: SampleService,
val someBodyProvider: (String) -> SomeBody
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(someBodyProvider(someValue))
}
}
And mock it in your tests:
val someValue = "foo"
val sampleService: SampleService = mock()
val someBody: SomeBody = mock()
val someBodyProvider: (String) -> SomeBody = mock {
on { invoke(someValue) }.thenReturn(someBody)
}
val userRequester = UserRequester(sampleService, someBodyProvider)
userRequester.doGetSomething("foo")
verify(sampleService).getSomething(someBody)
verify(someBodyProvider).invoke(someValue)
I used an anonymous function but you might as well make it an interface.

What is the best way to upload data to server and then store locally using RxJava

I want to do two things: upload some data to server and then save data to local database with returned id from server. What is the best way to do it with RxJava. I tired to do something like this but I guess it's not the best solution because of side effects in map and flatMap. I'm using Retrofit to communicate with server, ObjectBox as local database and ofcourse RxJava2
private fun saveMyData(data: MyData) =
uploadToServer(data).flatMap {
saveToLocalRepository(it)
}
private fun uploadToServer(data: MyData): Single<MyData> =
dataApiService.uploadMyData(data).map { id: Long ->
data.copy(serverId = it)
}
private fun saveToLocalRepository(data: MyData): Single<MyData> =
localRepository.save(data).map{ id: Long ->
data.copy(id = it)
}

Categories

Resources