I'm using RxJava alongside Retrofit and I'm trying to find a really clean way of abstracting my API calls in such a way that I can minimize code in the Activity.
So far, I have something like this:
Observable<SomeObservable> combined =
Observable.zip(firstObservable, secondObservable, SomeObservable::new);
// Works but needs header parsing
RxRestNetworker.performAsyncApiCall(combined, (returnData) -> {
// onNext() implementation
}, () -> hideSuggestionProgress());
The RxRestNetworker class looks like:
public class RxRestNetworker {
private static final String LOG_TAG = RxRestNetworker.class.getCanonicalName();
// Default error handling
private static Action1<Throwable> mOnError = throwable -> {
Log.e(LOG_TAG, throwable.getMessage());
throwable.printStackTrace();
};
/**
* Perform the desired request with default error handling
* #param observable
* #param onAction
* #param <T>
* #return
*/
public static <T> Subscription performAsyncApiCall(Observable<T> observable, Action1<? super T> onAction, Action0 onComplete) {
// Use default error handling
return performAsyncApiCall(observable, onAction, mOnError, onComplete);
}
/**
* Perform the desired request with some error handling OTHER than the default one
* #param observable
* #param onAction
* #param onError
* #param <T>
* #return
*/
public static <T> Subscription performAsyncApiCall(Observable<T> observable, Action1<? super T> onAction, Action1<Throwable> onError, Action0 onComplete) {
return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(onAction, onError, onComplete);
}
}
So you can see I'm using the default onError() by calling the first performAsyncApiCall method.
Now this all works, but I need to access the headers of the response by getting access to the response object.
This post claimed that I should wrap my response object and declare it like so in the API:
Observable<Response<SomeResponse>> getThings();
But I don't really care for that. It seems ugly and makes zipping observables like above a headache.
Ideally, I'd like to create an abstraction of the onNext() Action1 argument that can by default log the headers and do anything else done for every API call, then pass the results of the call back to the caller. Am I going in the wrong direction with this? Can somebody please help me find a way to abstract this in a very clean manner?
Thank you!
Related
I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a
nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result class, just a simple null check on the UI end would suffice.
// Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}
I have created my own lint Detector.visitCallExpression(UCallExpression) and I need to find a way to check if a MyClass class parameter passed into a method call is a child of MyParent class?
//Example having this below code somewhere to be Lint scanned.
someObject.method(MyClass.class)
How can I determine MyClass.class inherits from MyParent class?
//Using the IntelliJ InheritanceUtil utility class
//Converts argument of MyClass.class -> psiClass
InheritanceUtil.isInheritor(psiClass, "com.somePackage.MyParent")
The PsiClass I get from the MyClass.class parameter, is resolved to the base java.lang.Class object, so the InheritanceUtil check always return false~
Anyway i found the solution
/**
* Detects if a Class<?> => PsiClass:Class<?> is a subclass of a PsiClass(?)
*
* #param type The PsiType object that is a PsiClass of the class to be checked for subclass
* #param compareToParentCanonicalClass The Class canonical name to be compared as a super/parent class
*
* #return true if the PsiType is a subclass of compareToParentCanonicalClass, false otherwise
*/
open fun isPsiTypeSubClassOfParentClassType(type: PsiType, compareToParentCanonicalClass: String): Boolean {
println("superClass checking:$type")
var psiClss = PsiTypesUtil.getPsiClass(type)
val pct = type as PsiClassType
val psiTypes: List<PsiType> = ArrayList<PsiType>(pct.resolveGenerics().substitutor.substitutionMap.values)
for (i in psiTypes.indices) {
println("canonical:"+ psiTypes[i].canonicalText)
var psiClass = psiClss?.let { JavaPsiFacade.getInstance(it.project).findClass(psiTypes[i].canonicalText, psiClss.resolveScope) }
return InheritanceUtil.isInheritor(psiClass, compareToParentCanonicalClass)
}
return false;
}
I'm using Arrow in my Kotlin backend project. I have repositories like this:
interface UserRepository {
fun user(username: String): Try<Option<User>>
}
Now I want to go step further and abstract from concrete Try type with returning Kind<F, Option<User>> instead. I was able to do it with this code:
interface UserRepository<F> {
fun user(username: String): Kind<F, Option<User>>
}
class IdRepository : UserRepository<ForId> {
fun user(username: String): Kind<ForId<Option<User>>> =
if (username == "known") Id.just(Some(User()))
else Id.just(None)
}
But now I'm struggling to use it. I don't understand how we can say that the F in userRepository has to be a Monad, so that it can be used in monad comprehension block. Suppose I have some class defined like this:
class UserService<F>(ME: MonadError<F, Throwable>, repo: UserRepository<F>)
: MonadError<F, Throwable> by ME {
fun someOperations(username: String) : Kind<F, User> = bindingCatch {
val (user) = repo.user(username)
user.fold({ /* create user */ }, { /* return user */ })
}
}
The compiler complains that it can't bind user on line repo.user as it requires the Kind<ForTry, ...> but the repo.user returns Kind<F, ...> which is unknown here. How to properly achieve abstraction from Try so that I can implement repositories with Id instances and how to use such repositories in service classes?
In 0.10.0 you can use the Fx type class to perform monad bind. It's variants are available as described in the kdoc over your example where each one of them represents the level of power you want. In practice, most apps use IO.fx since effects can only be purely encapsulated in IO. You may only replace runtimes as long as they support suspension if you are working with side effects so this basically narrows down your runtime options to instances of Async<F> since suspension implies potential async work. That is IO, Rx, etc... but never Try, Either, ... those are good for eager non-effectful pure computations
/**
* Fx allows you to run pure sequential code as if it was imperative.
*
* #see [arrow.typeclasses.suspended.monad.Fx] // Anything with flatMap
* #see [arrow.typeclasses.suspended.monaderror.Fx] //Try, Either etc stop here
* #see [arrow.fx.typeclasses.suspended.monaddefer.Fx] // IO
* #see [arrow.fx.typeclasses.suspended.concurrent.Fx] // IO
*/
class UserService<F>(ME: MonadError<F, Throwable>, repo: UserRepository<F>)
: MonadError<F, Throwable> by ME {
fun someOperations(username: String) : Kind<F, User> =
fx.monadThrow {
val user = !repo.user(username)
user.fold({ /* create user */ }, { /* return user */ })
}
}
}
If you'd like a more detailed explanation swing by the https://slack.kotlinlang.org #arrow channel and we'll be happy to help and hang out and discuss FP in Kotlin
Cheers!
I have functions like the following in my code that return the object (for chaining):
/**
* Set properties
*
* #param $name
* #param $value
*
* #return \Boka10\Page\MenuItemConfig
*/
public function __set($name, $value)
{
$this->$name = $value;
return $this;
}
I want to move this function into a trait because, well, basically it is reused all the time.
My problem is, that the return $this line makes problems in the documentation section. In my PHPDoc it says (in this example) #return \Boka10\Page\MenuItemConfig.
How do I create a "global" trait method whose return documentation contains the correct typecast? Is it possible to do that or should I just add the __set function to each class?
I am not sure if I can explain what problem I am having here ;) What exactly would be the content of the #return tag in a globally used trait if all of these objects return their own instance?
After some digging and "hacking" I checked out the tests in Psalm and found, that the following seems to be the best method to comment these cases:
/**
* #method string somefunction($name, $value)
* #property string $name
*/
class ImplementTrait {
use MagicTrait;
}
The properties and methods are documented on the implementing class.
Note: I am using Psalm to check the code quality and this is psalms accepted way:
Psalm Tests for __call annotations
Psalm tests for __get/__set annotations
I use the following approach to add auto-completion in PHPStorm when using classes that implement some interfaces.
I create an interface with a skeleton of all target Trait public methods and extend it.
Example:
Trait MySimpleTrait {
/**
* #inheritDoc
*/
public function someMethod($someArg){
return doSomethingWithSomeArg($someArg);
}
}
Interface MySimpleTraitInterface {
/**
*
* This method do some thing with <var>$someArg</>
*
* #param mixed $someArg
* #return mixed
*/
public function someMethod($someArg);
}
interface MySimpleServiceInterface extends MySimpleTraitInterface
{
}
class MySimpleService implements MySimpleServiceInterface
{
use MySimpleTrait;
}
PHPStorm showing autocomplete for method defined in Trait:
PHPStorm showing phpDoc for method defined in Trait:
Seems absurd in 2022, but I have a lot of projects with PHP 5.6 and the syntax is supposed to be compatible (i.e., no typehint in non-scalar args or return functions).
How does one use a Mono.error(<Throwable>) but attach information from the body returned from a request?
Is there a reactive object that extends Throwable that takes a Mono/Flux object, so the error being thrown will wait for the body to be accounted for?
Or is there a way to add some sort of 'flag' onto an existing Mono object to make it fail instantly (to circumvent the requirement to be Throwable)
Example scenario below:
import org.springframework.web.reactive.function.client.WebClient;
private someMethod() {
webClient.get().retrieve().onStatus(HttpStatus::isError, this::errorHandler)
}
private Mono<? extends Throwable> errorHandler(ClientResponse cr) {
Mono<String> body = cr.body(BodyExtractors.toMono(String.class));
...<do something here>...
return Mono.error(new WhateverException());
}
Thanks
return body.flatMap(str -> Mono.error(new WhateverException(str)));