Is there any way to make retrofit2 return LiveData - android-architecture-components

Is there any way to make retrofit2 return livedata instead of Observable ?
If it's possible how we can implement this approach?
If it's not possible, Is it recommanded to make retrofit return Observable then convert it to livedata?

You have to add
addCallAdapterFactory(LiveDataCallAdapterFactory()) in Retrofit Adapter.
I have implemented using Dagger-2:-
#Module(includes = [ViewModelModule::class])
class AppModule {
#Singleton
#Provides
fun provideGithubService(): GithubService {
return Retrofit.Builder()
.baseUrl("YOUR URL")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.build()
.create(GithubService::class.java)
}
}
Create an interface class like GithubService:-
interface GithubService {
#GET("users/{login}")
fun getUser(#Path("login") login: String): LiveData<ApiResponse<User>>
}
Inject GithubService class and then you can get :-
githubService.getUser(login)
LiveDataCallAdapterFactory will be like this
import androidx.lifecycle.LiveData
import com.android.example.github.api.ApiResponse
import retrofit2.CallAdapter
import retrofit2.CallAdapter.Factory
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class LiveDataCallAdapterFactory : Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Factory.getRawType(returnType) != LiveData::class.java) {
return null
}
val observableType = Factory.getParameterUpperBound(0, returnType as ParameterizedType)
val rawObservableType = Factory.getRawType(observableType)
if (rawObservableType != ApiResponse::class.java) {
throw IllegalArgumentException("type must be a resource")
}
if (observableType !is ParameterizedType) {
throw IllegalArgumentException("resource must be parameterized")
}
val bodyType = Factory.getParameterUpperBound(0, observableType)
return LiveDataCallAdapter<Any>(bodyType)
}
}
For reference you can see this link https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample

You can use LiveDataReactiveStreams.
For example:
fun someMethod(id : Int) : LiveData<List<SomeObject>>{
return LiveDataReactiveStreams.fromPublisher(
repository.getDataFromSource(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
}
Here, repository.getDataFromSource() returns a Flowable.

Related

Spring boot serialize kotlin enum by custom property

I have an Enum and I would like to serialize it using custom property. It works in my tests but not when I make request.
Enum should be mapped using JsonValue
enum class PlantProtectionSortColumn(
#get:JsonValue val propertyName: String,
) {
NAME("name"),
REGISTRATION_NUMBER("registrationNumber");
}
In test the lowercase case works as expected.
class PlantProtectionSortColumnTest : ServiceSpec() {
#Autowired
lateinit var mapper: ObjectMapper
data class PlantProtectionSortColumnWrapper(
val sort: PlantProtectionSortColumn,
)
init {
// this works
test("Deserialize PlantProtectionSortColumn enum with custom name ") {
val json = """
{
"sort": "registrationNumber"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
// this one fails
test("Deserialize PlantProtectionSortColumn enum with enum name ") {
val json = """
{
"sort": "REGISTRATION_NUMBER"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
}
}
But in controller, when i send request with lowercase I get 400. But when the request matches the enum name It works, but response is returned with lowercase. So Spring is not using the objectMapper only for request, in response it is used.
private const val RESOURCE_PATH = "$API_PATH/plant-protection"
#RestController
#RequestMapping(RESOURCE_PATH, produces = [MediaType.APPLICATION_JSON_VALUE])
class PlantProtectionController() {
#GetMapping("/test")
fun get(
#RequestParam sortColumn: PlantProtectionSortColumn,
) = sortColumn
}
I believe kqr's answer is correct and you need to configure converter, not JSON deserializer.
It could look like:
#Component
class StringToPlantProtectionSortColumnConverter : Converter<String, PlantProtectionSortColumn> {
override fun convert(source: String): PlantProtectionSortColumn {
return PlantProtectionSortColumn.values().firstOrNull { it.propertyName == source }
?: throw NotFoundException(PlantProtectionSortColumn::class, source)
}}
In your endpoint you are not parsing json body but query parameters, which are not in json format.

Orientation change is not preserved while using viewmodel

New to MVVM clean archietecture .Building an app which has single screen consisting of Recycler view. The data is fetched through retrofit.According to the documentation ViewModel is able to live through the configuration changes but in my case it is not working when i change orientation from portrait to landscape. No clue about the issue, Please advise
**NewsViewModel.Kt**
import android.util.Log
import androidx.lifecycle.*
import com.example.recyclerviewjsonarray.model.NewsList
import com.example.recyclerviewjsonarray.network.remote.RetrofitInstanceDto
import com.example.recyclerviewjsonarray.network.remote.RetrofitServiceDto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private const val TAG ="NewsViewModel"
//viewmodel for handling clean archietecture
class NewsViewModel : ViewModel() {
//Mutable live data for the news list
private val _newsMutableLiveData: MutableLiveData<NewsList> = MutableLiveData()
val newsMutableLiveData : LiveData<NewsList> get() =
_newsMutableLiveData
//viewmodel will observe the latest updated data with the help of mutable live data
fun newsListObserver(): LiveData<NewsList> {
return newsMutableLiveData
}
/* making an api call using viewmodel scope (custom coroutines scope can be used as well)
launch is like a builder . Here it is launching Dispatcher.IO for memory intensive operation
Now inside we will create synchronized retrofit instance and fetch the response
in the form of getDataFromApi() with a delay of 2 seconds respectively
post value is called lastly for setting the value from a background thread */
fun getDataFromApi() {
Log.i(TAG,"init")
viewModelScope.launch(Dispatchers.IO) {
val retrofitInstance = RetrofitInstanceDto.getRetrofitInstance().create(RetrofitServiceDto::class.java)
val response = retrofitInstance.getDataFromApi()
delay(1500)
_newsMutableLiveData.postValue(response)
}
}
**NewsListFragment.kt**
import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.fragment.app.Fragment
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerviewjsonarray.R
import com.example.recyclerviewjsonarray.databinding.FragmentNewsListBinding
import com.example.recyclerviewjsonarray.model.NewsList
import kotlinx.android.synthetic.main.fragment_news_list.*
private const val TAG ="NewsListFragment"
//The view of MVVM architecture
class NewsListFragment : Fragment() {
/* view binding with late init as dont want to redraw again n again
also late init promises no nullable data when it is called later */
private lateinit var binding: FragmentNewsListBinding
//Kotlin property delegate used to define viewmodels
private val viewmodel: NewsViewModel by viewModels()
private lateinit var newsAdapter: NewsListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentNewsListBinding.inflate(layoutInflater)
Log.i(TAG,"onCreate")
//Creating the observer which updates the UI(Main Thread)
viewmodel.newsMutableLiveData.observe(viewLifecycleOwner, {
if(it!=null )
{
hideProgressBar()
Log.i(TAG,"Received the data")
initAdapterModel()
newsAdapter.setLatestData(it.rows,activity)
}
else {
showProgressBar()
Toast.makeText(activity,"No data",Toast.LENGTH_LONG).show()
}
})
viewmodel.getDataFromApi()
return binding.root
}
override fun onPause() {
super.onPause()
}
//Binding the recycler view adapter and with the news adapter
private fun initAdapterModel() {
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
newsAdapter = NewsListAdapter()
binding.recyclerView.adapter = newsAdapter
}
private fun hideProgressBar() {
progressBar.visibility = View.INVISIBLE
}
private fun showProgressBar() {
progressBar.visibility = View.VISIBLE
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.scrolltotop,menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
binding.recyclerView.smoothScrollToPosition(0)
return super.onOptionsItemSelected(item)
}
companion object {
#JvmStatic
fun newInstance() =
NewsListFragment()
}
}
}
Well I found the solution by using event and save instance state.

How to use feign interceptor / decoder to log request - response in custom format?

I'm developing a custom logging framework for springboot to log rest-template requests and response and is working fine. Am trying to implement the same for 'Feign-Client' and am faced with couple of issues.
For request logging, am leveraging FeignRequestInterceptor and it is working fine, only problem here is I cannot retrieve the full request URL.
Below method is giving me only relative URL.
requestTemplate.url()
To log the response, only way i could find was the ResponseDecoder. There I'm able to retrieve everything other than the payload. When accessing the payload from
InputStream is = response.body().asInputStream();
String payload = new String(IOUtils.toByteArray(is));
This method works, but the original stream is closed because of which logging happens fine, but client is throwing exception when returning response.
'trying to open closed stream'
I would like suggestions if there are better ways of logging request response in Feign similar to spring rest-template. Or if the method I have adopted is fine, help me resolve the problems above.
You can configure a custom feign.Logger instance to handle this. There are two built in, JavaLogger which uses java.util.logging and Slf4JLogger that uses slf4j. You can create your own logger implementation by extending feign.Logger and registering it as a #Bean.
That logger should be picked up by Spring and registered with your FeignClient. Here is the Logger base class to get you started:
protected abstract void log(String configKey, String format, Object... args);
Create your own instance, implement this method and it will be called before the request and after the response is returned. No need to update the interceptor or create a response decoder.
in your RestConfiguration you need to up default level of logging feignClient and override by #Bean feignLogger like:
#Configuration(proxyBeanMethods = false)
#EnableCircuitBreaker
#EnableFeignClients(basePackageClasses = [Application::class])
class RestConfiguration: WebMvcConfigurer {
#Bean
fun feignLoggerLevel(): Logger.Level {
return Logger.Level.FULL
}
#Bean
fun feignLogger(): Logger {
return FeignClientLogger()
}
}
and implement your logger (logbook format):
import feign.Logger
import feign.Request
import feign.Response
import feign.Util.*
import org.slf4j.LoggerFactory
class FeignClientLogger : Logger() {
private val log = LoggerFactory.getLogger(this::class.java)
override fun logRequest(configKey: String?, logLevel: Level?, request: Request?) {
if (request == null)
return
val feignRequest = FeignRequest()
feignRequest.method = request.httpMethod().name
feignRequest.url = request.url()
for (field in request.headers().keys) {
for (value in valuesOrEmpty(request.headers(), field)) {
feignRequest.addHeader(field, value)
}
}
if (request.requestBody() != null) {
feignRequest.body = request.requestBody().asString()
}
log.trace(feignRequest.toString())
}
override fun logAndRebufferResponse(
configKey: String?,
logLevel: Level?,
response: Response?,
elapsedTime: Long
): Response? {
if (response == null)
return response
val feignResponse = FeignResponse()
val status = response.status()
feignResponse.status = response.status()
feignResponse.reason =
(if (response.reason() != null && logLevel!! > Level.NONE) " " + response.reason() else "")
feignResponse.duration = elapsedTime
if (logLevel!!.ordinal >= Level.HEADERS.ordinal) {
for (field in response.headers().keys) {
for (value in valuesOrEmpty(response.headers(), field)) {
feignResponse.addHeader(field, value)
}
}
if (response.body() != null && !(status == 204 || status == 205)) {
val bodyData: ByteArray = toByteArray(response.body().asInputStream())
if (logLevel.ordinal >= Level.FULL.ordinal && bodyData.isNotEmpty()) {
feignResponse.body = decodeOrDefault(bodyData, UTF_8, "Binary data")
}
log.trace(feignResponse.toString())
return response.toBuilder().body(bodyData).build()
} else {
log.trace(feignResponse.toString())
}
}
return response
}
override fun log(p0: String?, p1: String?, vararg p2: Any?) {}
}
class FeignResponse {
var status = 0
var reason: String? = null
var duration: Long = 0
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"response","status":"$status","duration":"$duration","headers":$headers,"body":$body,"reason":"$reason"}"""
}
class FeignRequest {
var method: String? = null
var url: String? = null
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"request","method":"$method","url":"$url","headers":$headers,"body":$body}"""
}

Validation and DDD - kotlin data classes

In Java I would do validation when creating constructor in domain object, but when using data class from kotlin I don't know how to make similar validation. I could do that in application service, but I want to stick to domain object and it's logic. It's better to show on example.
public class Example {
private String name;
Example(String name) {
validateName(name);
this.name = name;
}
}
In Kotlin I have just a data class is there a way to do it similarly to Java style?
data class Example(val name: String)
You can put your validation code inside an initializer block. This will execute regardless of whether the object was instantiated via the primary constructor or via the copy method.
data class Example(val name: String) {
init {
require(name.isNotBlank()) { "Name is blank" }
}
}
A simple example:
fun main() {
println(Example(name = "Alice"))
println(try { Example(name = "") } catch (e: Exception) { e })
println(try { Example(name = "Bob").copy(name = "") } catch (e: Exception) { e })
}
Produces:
Example(name=Alice)
java.lang.IllegalArgumentException: Name is blank
java.lang.IllegalArgumentException: Name is blank
You can get a similar effect by using companion factory method
:
data class Example private constructor(val name: String) {
companion object {
operator fun invoke(name: String): Example {
//validateName
return Example(name)
}
}
}
...
val e = Example("name")
e.name //validated
You may want to use the interface to hide the data class.
The amount of code will increase slightly, but I think it's more powerful.
interface Example {
val id: String
val name: String
companion object {
operator fun invoke(name: String): Example {
// Validate ...
return ExampleData(
id = UUID.randomUUID().toString(),
name = name
)
}
}
fun copy(name: String): Example
operator fun component1() : String
operator fun component2() : String
}
private data class ExampleData(override val id: String, override val name: String): Example {
override fun copy(name: String): Example = Example(name)
}

Spring 5 Reactive - WebExceptionHandler is not getting called

I have tried all 3 solutions suggested in what is the right way to handle errors in spring-webflux, but WebExceptionHandler is not getting called. I am using Spring Boot 2.0.0.M7. Github repo here
#Configuration
class RoutesConfiguration {
#Autowired
private lateinit var testService: TestService
#Autowired
private lateinit var globalErrorHandler: GlobalErrorHandler
#Bean
fun routerFunction():
RouterFunction<ServerResponse> = router {
("/test").nest {
GET("/") {
ServerResponse.ok().body(testService.test())
}
}
}
}
#Component
class GlobalErrorHandler() : WebExceptionHandler {
companion object {
private val log = LoggerFactory.getLogger(GlobalErrorHandler::class.java)
}
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
log.info("inside handle")
/* Handle different exceptions here */
when(ex!!) {
is ClientException -> exchange!!.response.statusCode = HttpStatus.BAD_REQUEST
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
return Mono.empty()
}
}
UPDATE:
When I change Spring Boot version to 2.0.0.M2, the WebExceptionHandler is getting called. Do I need to do something for 2.0.0.M7?
SOLUTION:
As per Brian's suggestion, it worked as
#Bean
#Order(-2)
fun globalErrorHandler() = GlobalErrorHandler()
You can provide your own WebExceptionHandler, but you have to order it relatively to others, otherwise they might handle the error before yours get a chance to try.
the DefaultErrorWebExceptionHandler provided by Spring Boot for error handling (see reference documentation) is ordered at -1
the ResponseStatusExceptionHandler provided by Spring Framework is ordered at 0
So you can add #Order(-2) on your error handling component, to order it before the existing ones.
An error response should have standard payload info. This can be done by extending AbstractErrorWebExceptionHandler
ErrorResponse: Data Class
data class ErrorResponse(
val timestamp: String,
val path: String,
val status: Int,
val error: String,
val message: String
)
ServerResponseBuilder: 2 different methods to build an error response
default: handle standard errors
webClient: handle webClient exceptions (WebClientResponseException), not for this case
class ServerResponseBuilder(
private val request: ServerRequest,
private val status: HttpStatus) {
fun default(): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
status.value(),
status.name,
status.reasonPhrase)))
fun webClient(e: WebClientResponseException): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
e.statusCode.value(),
e.message.toString(),
e.responseBodyAsString)))
}
GlobalErrorHandlerConfiguration: Error handler
#Configuration
#Order(-2)
class GlobalErrorHandlerConfiguration #Autowired constructor(
errorAttributes: ErrorAttributes,
resourceProperties: ResourceProperties,
applicationContext: ApplicationContext,
viewResolversProvider: ObjectProvider<List<ViewResolver>>,
serverCodecConfigurer: ServerCodecConfigurer) :
AbstractErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
applicationContext
) {
init {
setViewResolvers(viewResolversProvider.getIfAvailable { emptyList() })
setMessageWriters(serverCodecConfigurer.writers)
setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> =
RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { response(it, errorAttributes) })
private fun response(request: ServerRequest, errorAttributes: ErrorAttributes?): Mono<ServerResponse> =
ServerResponseBuilder(request, status(request, errorAttributes)).default()
private fun status(request: ServerRequest, errorAttributes: ErrorAttributes?) =
HttpStatus.valueOf(errorAttributesMap(request, errorAttributes)["status"] as Int)
private fun errorAttributesMap(request: ServerRequest, errorAttributes: ErrorAttributes?) =
errorAttributes!!.getErrorAttributes(request, false)
}

Resources