How to implement Java step Builder pattern in Kotlin - android

In my current Android project I am investigating the use of Kotlin.
I am rewriting a 100% Java Android app to 100% Kotlin.
I'm stuck with attempting to implement my Java step builders though.
I employed Java step Builders as they force users of my code to supply all required data and/or functions before being able to execute an associated RxJava process.
These RxJava processes are complex and I wished to simplify their initialisation and execution as much as possible.
The use of Java Step builders allow developers to write the following code:-
Sequence.builder()
.stepOne(one)
.stepTwo(two)
.stepThree(three)
.build()
.execute();
What I am looking for is a Kotlin version of this approach.
My initial thought was that Kotlin would support Builders and Step Builders.
I am not "precious" about employing Builders in Kotlin, the Kotlin solution must force the developers that use my code to have supplied all required data and/or functions before they are able to execute the associated "executed" code.
From investigating Kotlin I've discovered internal DSLs which are sounding both interesting topic in themselves and a possible solution to this particular question.
I have a number of Step Builders to implement, none of these have more than 6 parameters. I do like to try and keep to SOLID no more than three parameters rule though.
Also if it makes any difference, some of the passed parameters are RxJava Actions and Consumers. Default values are not relevant here as none of the parameters have viable default values.
UPDATE
My Java step builders all resemble this:-
public class ExampleSequence extends Sequence {
private static final String TAG = "ExampleSequence";
private final Action onComplete;
private final Consumer<? super Throwable> onError;
/**
* #param builder
*/
private ExampleSequence(final Builder builder) {
super(builder.getDoLoginRefreshFail());
this.onError = builder.getOnError();
this.onComplete = builder.getOnComplete();
}
/**
*
*/
public static OnCompleteAction builder() {
return new Builder();
}
public interface OnCompleteAction {
onErrorAction onComplete(#NonNull final Action onComplete);
}
public interface onErrorAction {
DoLoginRefreshFail onError(#NonNull final Consumer<? super Throwable> onError);
}
public interface DoLoginRefreshFail {
Build doLoginRefreshFail(#NonNull final Action doLoginRefreshFail);
}
public interface Build {
ExampleSequence build();
}
#SuppressLint("CheckResult")
public void execute() {
final AtomicInteger retryCounter = new AtomicInteger(0);
final Observable<Response<GraphqlQueryResponse>> feedArticles = getPageAndNextInboxArticles(offset, limit)
.onErrorResumeNext(manufactureResumeNext())
.subscribeOn(Schedulers.io());
final Observable<Response<GraphqlQueryResponse>> readingListArticles = getPageAndReadingListArticles(readingListoffset, limit)
.onErrorResumeNext(manufactureResumeNext())
.subscribeOn(Schedulers.io());
login()
.flatMap(...)
.ignoreElement()
.andThen(...)
.andThen(...)
.ignoreElements()
.andThen(...)
.flattenAsObservable(x -> x)
.flatMapCompletable(...)
.retryWhen(errors -> errors.flatMap(e -> constructRetryHandler(retryCounter, e)))
.doOnComplete(onComplete)
.doOnError(onError)
.doAfterTerminate(doAfterTerminate())
.doOnSubscribe(compositeDisposable::add)
.blockingAwait();
}
/**********************************************************************************
*
* BUILDER
*
*/
public static class Builder implements OnCompleteAction, onErrorAction, DoLoginRefreshFail, Build {
private Action onComplete;
private Consumer<? super Throwable> onError;
private Action doLoginRefreshFail;
/***********************************************************************
*
*/
#Override
public ExampleSequence build() {
return new ExampleSequence(this);
}
#Override
public onErrorAction onComplete(#NonNull final Action onComplete) {
this.onComplete = onComplete;
return this;
}
#Override
public DoLoginRefreshFail onError(#NonNull final Consumer<? super Throwable> onError) {
this.onError = onError;
return this;
}
#Override
public Build doLoginRefreshFail(#NonNull final Action doLoginRefreshFail) {
this.doLoginRefreshFail = doLoginRefreshFail;
return this;
}
/**
* #return the onError
*/
Consumer<? super Throwable> getOnError() {
return onError;
}
/**
* #return the onComplete
*/
Action getOnComplete() {
return onComplete;
}
Action getDoLoginRefreshFail() {
return doLoginRefreshFail;
}
}
}

The step builder pattern in Kotlin is completely doable, and I've provided an example of it that mirrors the Java example you provided.
class ExampleSequence private constructor(builder: Builder): Sequence(builder.doLoginRefreshFail) { //This is your "super()" call.
//This is equivalent to assigning the final variables [onComplete] and [onError] in the class constructor
private val onComplete = builder.onComplete
private val onError = builder.onError
//More info about companion objects here: https://kotlinlang.org/docs/reference/object-declarations.html#companion-objects
companion object {
//Java will see this as [ExampleSequence.Companion.builder()] unless you add this annotation
#JvmStatic
fun builder(): OnCompleteAction = Builder()
}
fun execute() {
//Do your stuff here...
}
//The following classes and interfaces are similar to being static inner classes. If you want the classes to access
//fields of the enclosing outer class, you must use the keyword [inner] before declaring the class. Example:
// inner class Foo { ... }
interface OnCompleteAction {
fun onComplete(onComplete: Action): onErrorAction
}
interface DoLoginRefreshFail {
fun doLoginRefreshFail(doLoginRefreshFail: Action): Build
}
interface onErrorAction {
fun onError(onError: Consumer<in Throwable>): DoLoginRefreshFail //The [in] keyword is the same as saying Consumer<? super Throwable>
}
interface Build {
fun build(): ExampleSequence
}
class Builder: OnCompleteAction, onErrorAction, DoLoginRefreshFail, Build {
//The [lateinit] keyword states that this variable will be initialized later. Calling it before it is initialized will throw an exception
lateinit var onComplete: Action
private set //Only this class can modify.
lateinit var onError: Consumer<in Throwable>
private set
lateinit var doLoginRefreshFail: Action
private set
//No special differences here... oooh, inlined [override] keyword!
override fun onComplete(onComplete: Action): onErrorAction {
this.onComplete = onComplete
return this
}
override fun doLoginRefreshFail(doLoginRefreshFail: Action): Build {
this.doLoginRefreshFail = doLoginRefreshFail
return this
}
override fun onError(onError: Consumer<in Throwable>): DoLoginRefreshFail {
this.onError = onError
return this
}
override fun build(): ExampleSequence = ExampleSequence(this)
//Where are the getter methods? If you look at the variable declarations, they are public by default.
//This means that these variables are public read, but can only be set by this class only. In other words, built-in getter!
}
}
However, in a pure Kotlin project, step builder is sort of an anti-pattern. With default and named parameters built into the language, you can actually achieve SOLID by a simple data class. Taking the ExampleSequence class for example, your solution could look something like:
data class ExampleSequence(
private val onComplete: Action,
private val onError: Consumer<in Throwable>,
private val doLoginRefreshFail: Action,
private val aNewParam: String = "Default")
: Sequence(doLoginRefreshFail) { //This is your "super()" call.
fun execute() {
//Do your stuff here...
}
}
fun foo() {
//Example of using named parameters and passing in variables. Notice parameters aren't in the same order as how it is declared in the class
ExampleSequence(
onError = Consumer(),
onComplete = Action(),
doLoginRefreshFail = Action()
).execute()
//Since I added [aNewParam], instead of using the default, let's change it.
ExampleSequence(
onError = Consumer(),
onComplete = Action(),
doLoginRefreshFail = Action(),
aNewParam = "Something else!"
).execute()
}
Here is a nice article going into a bit more detail: https://dev.to/chrisvasqm/avoiding-the-builder-design-pattern-in-kotlin-3b1a
Also, in case you need another example of a step builder pattern in Kotlin, you might want to check this out too: https://www.baeldung.com/kotlin-builder-pattern

Related

How to use Mockito.mockStatic for mocking static methods in kotlin android

How to use Mockito.mockStatic for mocking static methods in kotlin android ?
This is my code:
class MyUtilClassTest {
#Test
fun testIsEnabled() {
Mockito.mockStatic(MyUtilClass::class.java, Mockito.CALLS_REAL_METHODS)
.use { mocked ->
mocked.`when`<Boolean> { MyUtilClass.isEnabled() }.thenReturn(true)
assertTrue(MyUtilClass.isEnabled())
}
}
}
object MyUtilClass {
fun isEnabled(): Boolean = false
}
I am getting this exception:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
you stub either of: final/private/equals()/hashCode() methods.
Those methods cannot be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
inside when() you don't call method on mock but on some other object.
If you annotate your function isEnabled with #JvmStatic, you won't get any error. As #Neige pointed out, static functions in Kotlin are actually not static in bytecode by default. Therefore, we need to mark our function with #JvmStatic in order to generate additional static getter/setter methods.
object MyUtilClass {
#JvmStatic
fun isEnabled(): Boolean = false
}
From the JVM point of view MyUtilClass.isEnabled() is not a static class/function.
You can use Show Kotlin Bytecode to understand what is behind
public final class MyUtilClass {
public static final MyUtilClass INSTANCE;
public final boolean isEnabled() {
return false;
}
private MyUtilClass() {
}
static {
MyUtilClass var0 = new MyUtilClass();
INSTANCE = var0;
}
}

How to test function inside RxJava flatMap?

I have the following method in the presenter.
public void addNote(int customerId, String body) {
disposables = RxUtil.initDisposables(disposables);
if (TextUtils.isEmpty(body)) {
view.showNoteTextEmpty();
return;
}
if (customerId == Constants.ZERO) {
view.showNoteError("There is a problem with adding note. Try again!");
return;
}
Disposable disposable = userPrefRepository.getLoggedInUser()
.subscribeOn(Schedulers.io())
.map(user -> getNote(body, user))
.flatMap(note -> customersRepository.addNote(customerId, note))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.isSuccessful()) {
view.onNoteAdded();
} else if (response.code() == 401) {
view.handleUnauthorisedError();
} else {
view.onNoteNotAdded();
}
}, view::handleError);
disposables.add(disposable);
}
Now I want to unit test it with the following class:
#RunWith(PowerMockRunner.class) #PrepareForTest(TextUtils.class)
public class NoteDetailsPresenterTest extends BaseTest {
#Rule TrampolineSchedulerRule trampolineSchedulerRule = new TrampolineSchedulerRule();
#Mock CustomersRepository customersRepository;
#Mock UserRepository userRepository;
#Mock RolesManager rolesManager;
#Mock NoteDetailsPresenter.View view;
private NoteDetailsPresenter presenter;
#Before
public void setUp() {
mockTextUtils();
presenter = new NoteDetailsPresenter(customersRepository, userRepository, rolesManager);
presenter.setView(view);
}
#Test
public void shouldAddNote() {
// Given
User user = User.newBuilder()
.withUserId(1)
.build();
// When
Mockito.when(userRepository.getLoggedInUser()).thenReturn(Single.just(user));
Note note = presenter.getNote("Note body", user);
Response<Note> response = Response.success(200, note);
Mockito.when(customersRepository.addNote(1, note)).thenReturn(Single.just(response));
presenter.addNote(1, "Note body");
// Then
Mockito.verify(view).onNoteAdded();
}
}
But it fails with the following exception:
Wanted but not invoked:
view.onNoteAdded();
-> at com.anstar.presentation.notes.NoteDetailsPresenterTest.shouldAddNote(NoteDetailsPresenterTest.java:56)
However, there were other interactions with this mock:
view.handleError(
java.lang.NullPointerException: The single returned by the mapper is null
);
-> at io.reactivex.internal.observers.ConsumerSingleObserver.onError(ConsumerSingleObserver.java:46)
How I can solve it? Is is the problem regarding map and flatMap transformations?
It seems that the mock can't be read. Try to put any() on the parameters:
Instead of this:
Mockito.when(customersRepository.addNote(1, note)).thenReturn(Single.just(response));
Use any():
Mockito.when(customersRepository.addNote(anyInt(), any(Note.class))).thenReturn(Single.just(response));
Why the mock can't read?
If parameters are primitive data types (string, int, double, etc) you can just pass the exact parameter (in your case, the first parameter which is integer, 1) and the mock will be read. However in objects (in your case, Note object), even though you have the same exact parameters, they will have different hashCode() so the mock can' t be read. Solution for this is to accept any() parameter specifying the class type: any(Note.class).

How to check if the result of an expression is used in a custom Android lint rule

I'm trying to write a lint rule to catch places where the result of an RxJava2 function is not used in anyway. For example:
final Observable<String> observable = getObservable();
observable.subscribe(this::onSuccess, this::onError);
In RxJava2, the subscribe function returns a Disposable that should be used to unsubscribe if the program/class instance "finishes" in some way in order to prevent memory leaks. I want to fail my build if any occurences like this are found.
This particular method (and all of the other ones I'm interested in) is annotated with io.reactivex.annotations.CheckReturnValue:
#CheckReturnValue
#SchedulerSupport(SchedulerSupport.NONE)
public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) {
return subscribe(onNext, onError, Functions.EMPTY_ACTION, Functions.emptyConsumer());
}
My plan is to write a custom lint rule that:
Searches for expressions that return the result of a method annotated with io.reactivex.annotations.CheckReturnValue
Filter the searches down to only expressions whose result is never used
For example, here are some cases that should not fail:
final CompositeDisposable compositeDisposable = new CompositeDisposable();
// Result of subscribe passed into another function
compositeDisposable.add(observable.subscribe(this::onSuccess, this::onError).dispose());
// Result of subscribe stored in a variable
final Disposable disposable = observable.subscribe(this::onSuccess, this::onError);
// Result of subscribe used
observable.subscribe(this::onSuccess, this::onError).dispose();
I've managed to write a lint rule that finds instances of call expressions where the result is annotated with CheckReturnValue, but I'm struggling to figure out how to use the JetBrains UAST/PSI APIs to work out if the result is used. This is my rule so far:
class RxJava2CheckReturnValueMethodNotAssigned : Detector(), Detector.UastScanner {
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
override fun createUastHandler(context: JavaContext) = CheckReturnValueVisitor(context)
class CheckReturnValueVisitor(private val context: JavaContext) : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
val method = node.resolve() ?: return
if (!isCheckReturnValueAnnotatedMethod(method)) {
return
}
if (!isResultOfCallUsed(node)) {
return
}
reportIssue(node)
}
private fun isCheckReturnValueAnnotatedMethod(method: PsiMethod): Boolean {
return context.evaluator.getAllAnnotations(method, true)
.any { "io.reactivex.annotations.CheckReturnValue" == it.qualifiedName }
}
private fun isResultOfCallUsed(node: UCallExpression): Boolean {
// Need to check is the result of the expression is used in some way
return false
}
private fun reportIssue(node: UCallExpression) {
// SNIP...
}
}
}
This currently doesn't work because it reports all usages of any function annotated with CheckReturnValue.
As far as I know, node.resolve() often return null

Observing viewmodel for the second time returns null in android

In my android app,im following architecture components with mvvm pattern.
my app makes a network call to display the weather information.api call is being made from repository which returns a livedata of response to the viewmodel,which inturn is observed by my main activity.
the app works fine except for one condition,whenever i disconnect the internet to test the fail case,it inflates error view as required
in the error view i have a retry button,which makes the method call to observe the viewmodel again(this method was also called by oncreate() for the first time,which worked)
even after switching on the internet,and clicking the retry button which listens for the observable.still the data becomes null.
i dont know why.please anyone help
REPOSITORY
#Singleton public class ContentRepository {
#Inject AppUtils mAppUtils;
private RESTService mApiService;
#Inject public ContentRepository(RESTService mApiService) {
this.mApiService = mApiService;
}
public MutableLiveData<ApiResponse<WeatherModel>> getWeatherListData() {
final MutableLiveData<ApiResponse<WeatherModel>> weatherListData = new MutableLiveData<>();
mApiService.getWeatherList().enqueue(new Callback<WeatherModel>() {
#Override public void onResponse(Call<WeatherModel> call, Response<WeatherModel> response) {
weatherListData.setValue(new ApiResponse<>(response.body()));
}
#Override public void onFailure(Call<WeatherModel> call, Throwable t) {
weatherListData.setValue(new ApiResponse<>(t));
}
});
return weatherListData;
}
}
VIEWMODEL
public class HomeViewModel extends AndroidViewModel {
private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;
#Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
this.weatherListObservable = contentRepository.getWeatherListData();
}
public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}
}
OBSERVE METHOD IN ACTIVITY
private void observeViewModel() {
mHomeViewModel = ViewModelProviders.of(this, mViewModelFactory).get(HomeViewModel.class);
mHomeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
if (weatherModelApiResponse.isSuccessful()) {
mErrorView.setVisibility(View.GONE);
mBinding.ivLoading.setVisibility(View.GONE);
try {
setDataToViews(weatherModelApiResponse.getData());
} catch (ParseException e) {
e.printStackTrace();
}
} else if (!weatherModelApiResponse.isSuccessful()) {
mBinding.ivLoading.setVisibility(View.GONE);
mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
mErrorView.setVisibility(View.VISIBLE);
}
});
}
RETRY BUTTON IN ACTIVITY
#Override public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_retry:
mErrorView.setVisibility(View.GONE);
observeViewModel();
break;
}
}
Updated:- 5 December 2017
I was fortunate to meet Lyla Fujiwara, during Google Developer Days, India where I asked her the same question. She suggested me to user Transformations.switchMap(). Following is the updated solution -
#Singleton
public class SplashScreenViewModel extends AndroidViewModel {
private final APIClient apiClient;
// This is the observable which listens for the changes
// Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class
// you can add that here
private MutableLiveData<Void> networkInfoObservable;
// This LiveData contains the information required to populate the UI
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = new MutableLiveData<Void>();
// Using the Transformation switchMap to listen when the data changes happen, whenever data
// changes happen, we update the LiveData object which we are observing in the MainActivity.
networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation());
}
/**
* Function to get LiveData Observable for NetworkInformation class
* #return LiveData<Resource<NetworkInformation>>
*/
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
/**
* Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value
* which in turn calls the `Transformations.switchMap()` function and updates the data and we get
* call back
*/
public void setNetworkInformation() {
networkInfoObservable.setValue(null);
}
}
The Activity's code will be updated as -
final SplashScreenViewModel splashScreenViewModel =
ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class);
observeViewModel(splashScreenViewModel);
// This function will ensure that Transformation.switchMap() function is called
splashScreenViewModel.setNetworkInformation();
This looks the most prominent and proper solution to me for now, I will update the answer if I better solution later.
Watch her droidCon NYC video for more information on LiveData. The official Google repository for LiveData is https://github.com/googlesamples/android-architecture-components/ look for GithubBrowserSample project.
Old Code
I have not been able find a proper solution to this, but this works so far -
Declare ViewModel outside the observeViewModel() and change the function like this -
private void observeViewModel(final HomeViewModel homeViewModel) {
homeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
if (weatherModelApiResponse.isSuccessful()) {
mErrorView.setVisibility(View.GONE);
mBinding.ivLoading.setVisibility(View.GONE);
try {
setDataToViews(weatherModelApiResponse.getData());
} catch (ParseException e) {
e.printStackTrace();
}
} else if (!weatherModelApiResponse.isSuccessful()) {
mBinding.ivLoading.setVisibility(View.GONE);
mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
mErrorView.setVisibility(View.VISIBLE);
}
});
}
Update HomeViewModel to -
public class HomeViewModel extends AndroidViewModel {
private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;
#Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
getWeattherListData();
}
public void getWeatherListData() {
this.weatherListObservable = contentRepository.getWeatherListData();
}
public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}
}
Now Retry button, call the observeViewModel function again and pass mHomeViewModel to it. Now you should be able to get a response.

Is it possible to enforce non-nullability of LiveData values?

Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have #Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:
public interface Observer<T> {
/**
* Called when the data is changed.
* #param t The new data
*/
void onChanged(#Nullable T t);
}
A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.
Instead of using:
class MyViewModel {
val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}
class MyFragment: Fragment() {
model.data.observe(viewLifecycleOwner) {
// ...
}
}
You can use:
class MyViewModel {
val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}
class MyFragment: Fragment() {
lifecycleScope.launch {
model.data.collect {
// ...
}
}
}
StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Note that this API has been experimental before coroutines 1.4.0.
Here's some additional reading about replacing LiveData with StateFlow.
As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.
If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/#henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with #NonNull and you can be sure that the data has already been checked before reaching the Observer.
Wrap the LiveData class:
public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {
private final #NonNull T initialValue;
public NonNullMutableLiveData(#NonNull T initialValue) {
this.initialValue = initialValue;
}
#Override
public void postValue(#NonNull T value) {
super.postValue(value);
}
#Override
public void setValue(#NonNull T value) {
super.setValue(value);
}
#NonNull
#Override
public T getValue() {
//the only way value can be null is if the value hasn't been set yet.
//for the other cases the set and post methods perform nullability checks.
T value = super.getValue();
return value != null ? value : initialValue;
}
//convenience method
//call this method if T is a collection and you modify it's content
public void notifyContentChanged() {
postValue(getValue());
}
public void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer) {
super.observe(owner, observer.getObserver());
}
}
Create an interface for exposing as immutable:
public interface NonNullLiveData<T> {
#NonNull T getValue();
void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer);
}
Finally, wrap the Observer:
//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {
public Observer<T> getObserver() {
return new ActualObserver();
}
public abstract void onValueChanged(#NonNull T t);
private class ActualObserver implements Observer<T> {
#Override
public void onChanged(#Nullable T t) {
//only called through NonNullMutableLiveData so nullability check has already been performed.
//noinspection ConstantConditions
onValueChanged(t);
}
}
}
Now you can create your data like this:
class DataSource {
private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
public NonNullLiveData<Integer> getData() {
return data;
}
}
And use it like this:
dataSource.getData().observe(this, new NonNullObserver<Integer>() {
#Override
public void onValueChanged(#NonNull Integer integer) {
}
});
Completely null safe.
While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).
Assert
You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(#Nullable Integer integer) {
assert integer != null;
Log.d("Example", integer.toString());
}
}
Suppress the warning
Add an annotation to suppress the warning.
class PrintObserver implements Observer<Integer> {
#Override
#SuppressWarnings("ConstantConditions")
public void onChanged(#Nullable Integer integer) {
Log.d("Example", integer.toString());
}
}
Remove the annotation
This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the #Nullable annotation from the implementation:
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(Integer integer) {
Log.d("Example", integer.toString());
}
}
Default methods
It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:
interface NonNullObserver<V> extends Observer<V> {
#Override
default void onChanged(#Nullable V v) {
Objects.requireNonNull(v);
onNonNullChanged(v);
// Alternatively, you could add an if check here.
}
void onNonNullChanged(#NonNull V value);
}
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner, Observer {
it?.let(observer)
})
}
You would have to do some additional work to handle null values that come from the library itself.
For example, when you return a LiveData from a #Dao in Room, like:
#Dao interface UserDao {
#get:Query("SELECT * FROM users LIMIT 1")
val user: LiveData<User>
}
And observe the user live data, it will call the onChanged callback with a null value if there is no user.

Categories

Resources