Is there a way to bind property of the VM to any kind of singleton property ( static resource property, property in the singleton service... ) in a way that we don't need to use IMessenger or to handle SingletonServiceResolved OnPropertyChanged?
It feels kind of dirty for me (even if it is in the base class) to have each activity to handle changes in my singleton Clock Property.
public class ClockService : ObservableObject, IClockService {
private DateTime _clock;
public DateTime Clock {
get{ return _clock;}
set { _clock = value; RaisePropertyChanged("Clock"); }
}
}
public class SomeViewModel : BaseViewModel {
private IClockService _clockService;
private IMvxMessenger _messenger;
public SomeViewModel(IClockService clockService, IMvxMessenger messenger) {
_clockService=clockService;
_messenger = messenger;
//trying to avoid
clockService.PropertyChanged += OnClockServicePropertyChanged;
}
public DateTime MyClock {
get{return _clockService.Clock;}
}
private OnClockServicePropertyChanged(...) {
if(e.PropertyName=="Clock") RaisePropertyChanged("Clock");
}
}
One way to bind to a singleton is to expose the singleton via a property on your ViewModel:
public Thing MySingleton
{
get
{
return Thing.Instance;
}
}
Update after comment that the singleton isn't constant:
You could use another constant singleton as a holder - and this can then implement INotifyPropertyChanged - e.g.:
public class ThingHolder : MvxNotifyPropertyChanged
{
public static readonly ThingHolder Instance = new ThingHolder();
private Thing _thing;
public Thing CurrentThing
{
get { return _thing; }
set { _thing = value; RaisePropertyChanged(() => CurrentThing); }
}
}
Your VMs can then properties like:
public ThingHolder ThingHolder
{
get
{
return ThingHolder.Instance;
}
}
Your views can then bind to expressions like ThingHolder.CurrentThing
Related
I'm using ReactiveUI 8.7.2 to create android/iOS Xamarin.Native app with shared ViewModels. I want to use WhenActivated in Fragments and corresponding ViewModels, but it's only being called in Fragments. Here's my code:
Base ViewModel:
public class ReactiveViewModel : ReactiveObject, ISupportsActivation
{
public ViewModelActivator Activator { get; }
public ReactiveViewModel(ViewModelActivator viewModelActivator)
{
Activator = viewModelActivator;
this.WhenActivated((Action<IDisposable> disposable) =>
{
Console.WriteLine("Activated BaseViewModel");
});
}
public ReactiveViewModel() : this(new ViewModelActivator())
{
}
}
Base Fragment:
public abstract class BaseReactiveFragment<T> : ReactiveUI.AndroidSupport.ReactiveFragment<T> where T : ReactiveViewModel
{
public BaseReactiveFragment()
{
this.WhenActivated(disposable =>
{
Console.WriteLine("Activated BaseFrament");
});
}
}
ViewModel
public class MyViewModel : ReactiveViewModel
{
public MyViewModel()
{
this.WhenActivated((Action<IDisposable> disposable) =>
{
Console.WriteLine("Activated ViewModel");
});
}
}
Fragment
public class MyFragment : BaseReactiveFragment<MyViewModel>
{
public MyFragment()
{
this.WhenActivated((Action<IDisposable> disposable)=>
{
Console.WriteLine("Activated Fragment");
});
}
}
What am I missing?
[Posting an extended version of my comment as an answer for completeness.]
Make sure you assign the ViewModel property of MyFragment/BaseReactiveFragment. The ViewModel property is only automatically assigned if you're using ReactiveUI's routing infrastructure e.g. RoutedViewHost or ViewModelViewHost. But because Android uses a different mechanism (intents) for Activity construction, ReactiveUI.Android doesn't include those helpers.
Glad to hear it was an easy fix. Happy coding!
Currently I'm using MockK library (version 1.8.1) for unit tests in Android Dev, and I the problem is I can't mock Patterns.EMAIL_ADDRESS.
Test cases throw NPE every time this property gets invoked.
I tried mockkStatic(Patterns::class), but #Before method crashes with NPE while applying the rule every { Patterns.EMAIL_ADDRESS.pattern() } returns EMAIL_REGEX_STRING.
Class I'm trying to test:
public class EmailValidator {
private static final String EMPTY = "";
private final Context context;
#Inject
public EmailValidator(Context context) {
this.context = context;
}
public String isValidEmail(String email) {
if (StringUtils.isEmpty(email)) {
return context.getString(R.string.sign_up_error_email_empty);
}
if (!email.matches(Patterns.EMAIL_ADDRESS.pattern())) {
return context.getString(R.string.sign_up_error_email_validate);
}
return EMPTY;
}}
Try using
PatternsCompat.EMAIL_ADDRESS.pattern()
instead of just
Patterns.EMAIL_ADDRESS.pattern()
that did the job for me.
Instead of using Patterns.EMAIL_ADDRESS directly, you could create a wrapper around it then mock or fake the wrapper.
The wrapper could be a method, like:
class EmailValidator {
fun isValidEmail(email: String) {
if (StringUtils.isEmpty(email)) {
return context.getString(R.string.sign_up_error_email_empty);
}
if (!email.matches(getEmailPattern())) {
return context.getString(R.string.sign_up_error_email_validate);
}
}
private fun getEmailPattern(): String = Patterns.EMAIL_ADDRESS.pattern()
}
and your test could mock it like:
#Test
fun `test email validator`() {
val validator = spyk(EmailValidator())
every { validator["getEmailPattern"]() } returns yourTestPattern
assertThat(validator.isValidEmail("blah blah blah")).isFalse()
}
Or create a class that wraps it, maybe a PatternFactory class
class PatternFactory {
fun getEmailPattern(): String = ...
fun getVinPattern(): String = ...
}
then pass in PatternFactory in as a dependency and mock it for the test
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.
I have a problem with the dependency services for implementing features that depends of the plattorm. I need what my implementation on Android receive a Context object to do the task. How can I do it?
This is my code:
1) On PCL:
public interface ICallService
{
List<string> GetContacts();
}
2) On Android Project:
[assembly: Dependency(typeof(CallService))]
namespace DEMOBLOBS.Droid.DependencyServicesPruebas
{
public class CallService : ICallService
{
public static void Init() { }
public List<string> GetContacts()
{
AT THIS POINT I NEED THE CONTEXT OBJECT!
}
}
}
The constructor of Call Service class does not have any parameter. Maybe I can I pass the Context object like parameter in some way?
Can you help me, please?
you could try answer from https://forums.xamarin.com/discussion/106938/context-is-obsolete-as-of-version-2-5
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle bundle)
{
Instance = this;
// Forms initialization here...
}
//later where you need it:
var context = MainActivity.Instance;
I am facing with Unit testing for the first time and I would like to know what is the best approach for the following scenario.
I am using Mockito for the tests. The following test is for logic(Presenter) layer and I am trying to verify certain behaviors of the view.
App classes
The method of the Presenter that need to be include in the test:
public void loadWeather() {
CityDetailsModel selectedCity = getDbHelper().getSelectedCityModel();
if (selectedCity != null) {
getCompositeDisposableHelper().execute(
getApiHelper().weatherApiRequest(selectedCity.getLatitude(), selectedCity.getLongitude()),
new WeatherObserver(getMvpView()));
} else {
getMvpView().showEmptyView();
}
}
WeatherObserver:
public class WeatherObserver extends BaseViewSubscriber<DayMvpView, WeatherResponseModel> {
public WeatherObserver(DayMvpView view) {
super(view);
}
#Override public void onNext(WeatherResponseModel weatherResponseModel) {
super.onNext(weatherResponseModel);
if (weatherResponseModel.getData().isEmpty()) {
getMvpView().showEmptyView();
} else {
getMvpView().showWeather(weatherResponseModel.getData());
}
}
}
BaseViewSubscriber (Default DisposableObserver base class to be used whenever we want default error handling):
public class BaseViewSubscriber<V extends BaseMvpView, T> extends DisposableObserver<T> {
private ErrorHandlerHelper errorHandlerHelper;
private V view;
public BaseViewSubscriber(V view) {
this.view = view;
errorHandlerHelper = WeatherApplication.getApplicationComponent().errorHelper();
}
public V getView() {
return view;
}
public boolean shouldShowError() {
return true;
}
protected boolean shouldShowLoading() {
return true;
}
#Override public void onStart() {
if (!AppUtils.isNetworkAvailable(WeatherApplication.getApplicationComponent().context())) {
onError(new InternetConnectionException());
return;
}
if (shouldShowLoading()) {
view.showLoading();
}
super.onStart();
}
#Override public void onError(Throwable e) {
if (view == null) {
return;
}
if (shouldShowLoading()) {
view.hideLoading();
}
if (shouldShowError()) {
view.onError(errorHandlerHelper.getProperErrorMessage(e));
}
}
#Override public void onComplete() {
if (view == null) {
return;
}
if (shouldShowLoading()) {
view.hideLoading();
}
}
#Override public void onNext(T t) {
if (view == null) {
return;
}
}
}
CompositeDisposableHelper (CompositeDisposable helper class):
public class CompositeDisposableHelper {
public CompositeDisposable disposables;
public TestScheduler testScheduler;
#Inject public CompositeDisposableHelper(CompositeDisposable disposables) {
this.disposables = disposables;
testScheduler = new TestScheduler();
}
public <T> void execute(Observable<T> observable, DisposableObserver<T> observer) {
addDisposable(observable.subscribeOn(testScheduler)
.observeOn(testScheduler)
.subscribeWith(observer));
}
public void dispose() {
if (!disposables.isDisposed()) {
disposables.dispose();
}
}
public TestScheduler getTestScheduler() {
return testScheduler;
}
public void addDisposable(Disposable disposable) {
disposables.add(disposable);
}
}
My test:
#Test public void loadSuccessfully() {
WeatherResponseModel responseModel = new WeatherResponseModel();
List<WeatherModel> list = new ArrayList<>();
list.add(new WeatherModel());
responseModel.setData(list);
CityDetailsModel cityDetailsModel = new CityDetailsModel();
cityDetailsModel.setLongitude("");
cityDetailsModel.setLatitude("");
when(dbHelper.getSelectedCityModel()).thenReturn(cityDetailsModel);
when(apiHelper.weatherApiRequest(anyString(), anyString())).thenReturn(
Observable.just(responseModel));
dayPresenter.loadWeather();
compositeDisposableHelper.getTestScheduler().triggerActions();
verify(dayMvpView).showWeather(list);
verify(dayMvpView, never()).showEmptyView();
verify(dayMvpView, never()).onError(anyString());
}
When I try to run the test, I get NullPointer, because new WeatherObserver(getMvpView()) is called, and in the BaseViewSubscriber errorHandlerHelper is null because getApplicationCopomnent is null.
As well NullPointer is thrown in the static method AppUtils.isNetworkAvailable() for the same reason.
When I try to comment these lines, the test is OK.
My questions are:
Should I use Dagger for the Unit test as well or? If yes please give
me example for my test.
Should I use PowerMockito for the static method
AppUtils.isNetworkAvailable()? If yes, is it ok just because of
this method to use PowerMockito Runner
#RunWith(PowerMockRunner.class)?
Should I use Dagger for the Unit test as well or? If yes please give me example for my test.
You don't have to use Dagger necessarily at the test, but that's where Dependency Injection will benefit you, as it will help you strip your dependencies out, and tests will be able to replace them.
Should I use PowerMockito for the static method AppUtils.isNetworkAvailable()? If yes, is it ok just because of this method to use PowerMockito Runner
#RunWith(PowerMockRunner.class)?
Static methods are generally bad for testing, as you cannot replace them (at least not easily and without PowerMock) for testing purposes.
The better practice is to use Dagger for the production code to inject those dependencies, preferably at Constructor, so at tests you can simply provide those dependencies according to test needs (using mocks or fakes where necessary).
In your case, you can add both ErrorHandlerHelper and AppUtils to BaseViewSubscriber Constructor. as BaseViewSubscriber shouldn't be injected, you will need to provide those modules to it from outside, in the presenter, that where you should use Injection to get those Objects. again at the Constructor.
At test, simply replace or provide this objects to the presenter that in it's turn will hand it over to the BaseViewSubscriber.
You can read more about tests seams at android here.
Besides that, it some very odd to me the OO hierarchy of Observer and Disposable that wraps the Observable for getting common behavior, it's essentially breaking the functional stream oriented reactive approach, you might want to consider using patterns like compose using Transformers and using doOnXXX operators do apply common behavior at reactive streams.