Create an instance of WindowInsetsCompat - android

Core version: androidx.core:core:1.0.1
Is there any proper way for creating WindowInsetsCompat's instance?
As I see, it has the private constructor:
private WindowInsetsCompat(Object insets) {
mInsets = insets;
}
It's called from 5 methods and 1 static wrapper, that has package-private visibility:
static WindowInsetsCompat wrap(Object insets) {
return insets == null ? null : new WindowInsetsCompat(insets);
}
That wrap method only used at 6 methods inside ViewCompat, that's all.
So, can we create an instance of WindowInsetsCompat somehow?
Or, the only way is cmd+c/cmd+v?

You have two solutions to create an instance of WindowInsetsCompat.
First solution
You can use reflection to call the private constructor:
package com.example.myapplication;
import androidx.core.view.WindowInsetsCompat;
import java.lang.reflect.Constructor;
public class WindowInsetsCompatHelper {
public static WindowInsetsCompat createWindowInsetsCompat(Object insets) {
try {
Constructor<WindowInsetsCompat> constructor = WindowInsetsCompat.class.getDeclaredConstructor(Object.class);
constructor.setAccessible(true);
return constructor.newInstance(insets);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Second solution
It requires to create a helper class in the package androidx.core.view. After that you can directly use the wrap method:
package androidx.core.view;
public class WindowInsetsCompatHelper {
public static WindowInsetsCompat createWindowInsetsCompat(Object insets) {
return WindowInsetsCompat.wrap(insets);
}
}

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;
}
}

Mockito unit test BaseObserver and static method with context

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.

Checking toast message in android espresso

Would anyone know how to test for the appearance of a Toast message in android espresso? In robotium its easy & I used but started working in espresso but dont getting the exact command.
This slightly long statement works for me:
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
....
onView(withText(R.string.TOAST_STRING)).inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));
The accepted answer is a good one but didn't work for me. So I searched a bit and found this blog article.
This gave me an idea of how to do it and I updated the solution above.
First I implemented the ToastMatcher:
import android.os.IBinder;
import android.support.test.espresso.Root;
import android.view.WindowManager;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class ToastMatcher extends TypeSafeMatcher<Root> {
#Override
public void describeTo(Description description) {
description.appendText("is toast");
}
#Override
public boolean matchesSafely(Root root) {
int type = root.getWindowLayoutParams().get().type;
if (type == WindowManager.LayoutParams.TYPE_TOAST) {
IBinder windowToken = root.getDecorView().getWindowToken();
IBinder appToken = root.getDecorView().getApplicationWindowToken();
if (windowToken == appToken) {
// windowToken == appToken means this window isn't contained by any other windows.
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
return true;
}
}
return false;
}
}
Then I implemented my check methods like this:
public void isToastMessageDisplayed(int textId) {
onView(withText(textId)).inRoot(MobileViewMatchers.isToast()).check(matches(isDisplayed()));
}
MobileViewMatchers is a container for accessing the matchers. There I defined the static method isToast().
public static Matcher<Root> isToast() {
return new ToastMatcher();
}
This works like a charm for me.
First make sure to import:
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
Inside your class you probably have a rule like this:
#Rule
public ActivityTestRule<MyNameActivity> activityTestRule =
new ActivityTestRule<>(MyNameActivity.class);
Inside your test:
MyNameActivity activity = activityTestRule.getActivity();
onView(withText(R.string.toast_text)).
inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
check(matches(isDisplayed()));
This worked for me, and it was pretty easy to use.
If you're using the newest Android Testing Tools from Jetpack, you know, that ActivityTestRule is deprecated and you should use ActivityScenario or ActivityScenarioRule(which contains the first).
Prerequisites. Create decorView variable and assign it before tests;
#Rule
public ActivityScenarioRule<FeedActivity> activityScenarioRule = new ActivityScenarioRule<>(FeedActivity.class);
private View decorView;
#Before
public void setUp() {
activityScenarioRule.getScenario().onActivity(new ActivityScenario.ActivityAction<FeedActivity>() {
#Override
public void perform(FeedActivityactivity activity) {
decorView = activity.getWindow().getDecorView();
}
});
}
Test itself
#Test
public void given_when_thenShouldShowToast() {
String expectedWarning = getApplicationContext().getString(R.string.error_empty_list);
onView(withId(R.id.button))
.perform(click());
onView(withText(expectedWarning))
.inRoot(withDecorView(not(decorView)))// Here we use decorView
.check(matches(isDisplayed()));
}
getApplicationContext() can be taken from androidx.test.core.app.ApplicationProvider.getApplicationContext;
First create a cutom Toast Matcher which we can use in our test cases -
public class ToastMatcher extends TypeSafeMatcher<Root> {
#Override public void describeTo(Description description) {
description.appendText("is toast");
}
#Override public boolean matchesSafely(Root root) {
int type = root.getWindowLayoutParams().get().type;
if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
IBinder windowToken = root.getDecorView().getWindowToken();
IBinder appToken = root.getDecorView().getApplicationWindowToken();
if (windowToken == appToken) {
//means this window isn't contained by any other windows.
return true;
}
}
return false;
}
}
1. Test if the Toast Message is Displayed
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(isDisplayed()));
2. Test if the Toast Message is not Displayed
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(not(isDisplayed())));
3. Test id the Toast contains specific Text Message
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(withText("Invalid Name"));
Thanks,
Anuja
Note - this answer is from This POST.
Though the question has an accepted answer - which BTW does not work for me - I'd like to add my solution in Kotlin which I derived from Thomas R.'s answer:
package somepkg
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Root
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import android.view.WindowManager.LayoutParams.TYPE_TOAST
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
/**
* This class allows to match Toast messages in tests with Espresso.
*
* Idea taken from: https://stackoverflow.com/a/33387980
*
* Usage in test class:
*
* import somepkg.ToastMatcher.Companion.onToast
*
* // To assert a toast does *not* pop up:
* onToast("text").check(doesNotExist())
* onToast(textId).check(doesNotExist())
*
* // To assert a toast does pop up:
* onToast("text").check(matches(isDisplayed()))
* onToast(textId).check(matches(isDisplayed()))
*/
class ToastMatcher(private val maxFailures: Int = DEFAULT_MAX_FAILURES) : TypeSafeMatcher<Root>() {
/** Restrict number of false results from matchesSafely to avoid endless loop */
private var failures = 0
override fun describeTo(description: Description) {
description.appendText("is toast")
}
public override fun matchesSafely(root: Root): Boolean {
val type = root.windowLayoutParams.get().type
#Suppress("DEPRECATION") // TYPE_TOAST is deprecated in favor of TYPE_APPLICATION_OVERLAY
if (type == TYPE_TOAST || type == TYPE_APPLICATION_OVERLAY) {
val windowToken = root.decorView.windowToken
val appToken = root.decorView.applicationWindowToken
if (windowToken === appToken) {
// windowToken == appToken means this window isn't contained by any other windows.
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
return true
}
}
// Method is called again if false is returned which is useful because a toast may take some time to pop up. But for
// obvious reasons an infinite wait isn't of help. So false is only returned as often as maxFailures specifies.
return (++failures >= maxFailures)
}
companion object {
/** Default for maximum number of retries to wait for the toast to pop up */
private const val DEFAULT_MAX_FAILURES = 5
fun onToast(text: String, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(text)).inRoot(isToast(maxRetries))!!
fun onToast(textId: Int, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(textId)).inRoot(isToast(maxRetries))!!
fun isToast(maxRetries: Int = DEFAULT_MAX_FAILURES): Matcher<Root> {
return ToastMatcher(maxRetries)
}
}
}
I hope this will be of help for later readers - the usage is described in the comment.
I write my custom toast matcher:
import android.view.WindowManager
import androidx.test.espresso.Root
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
class ToastMatcher : TypeSafeMatcher<Root>() {
override fun describeTo(description: Description) {
description.appendText("is toast")
}
override fun matchesSafely(root: Root): Boolean {
val type = root.getWindowLayoutParams().get().type
if (type == WindowManager.LayoutParams.TYPE_TOAST) {
val windowToken = root.getDecorView().getWindowToken()
val appToken = root.getDecorView().getApplicationWindowToken()
if (windowToken === appToken) {
return true
}
}
return false
}
}
And use like this:
onView(withText(R.string.please_input_all_fields)).inRoot(ToastMatcher()).check(matches(isDisplayed()))
For kotlin, I had to use the apply extension function, and this worked for me.
1- declare your ToastMatcher class in the androidTest folder:
class ToastMatcher : TypeSafeMatcher<Root?>() {
override fun matchesSafely(item: Root?): Boolean {
val type: Int? = item?.windowLayoutParams?.get()?.type
if (type == WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) {
val windowToken: IBinder = item.decorView.windowToken
val appToken: IBinder = item.decorView.applicationWindowToken
if (windowToken === appToken) { // means this window isn't contained by any other windows.
return true
}
}
return false
}
override fun describeTo(description: Description?) {
description?.appendText("is toast")
}
}
2- Then you use like this to test that the toast message actually displays
onView(withText(R.string.invalid_phone_number))
.inRoot(ToastMatcher().apply {
matches(isDisplayed())
});
Attribution to ToastMatcher class:
/**
* Author: http://www.qaautomated.com/2016/01/how-to-test-toast-message-using-espresso.html
*/
I would say for toast messages first define your rule
#Rule
public ActivityTestRule<AuthActivity> activityTestRule =
new ActivityTestRule<>(AuthActivity.class);
then whatever toast message text you are looking for type it in between quotation
for example I used "Invalid email address"
onView(withText("Invalid email address"))
.inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
.check(matches(isDisplayed()));
I would like to suggest an alternative method, especially if you need to check that particular toast is NOT displayed
The problem here that
onView(viewMatcher)
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(not(isDisplayed())))
or
onView(viewMatcher)
.inRoot(RootMatchers.isPlatformPopup())
.check(doesNotExist())
or any other custom inRoot checks
are throwing NoMatchingRootException even before the code passes to check method
You may just catch the exception and complete the test but that's not a good option since throwing and catching NoMatchingRootException consumes a lot of time in a comparison with the default test case. Seems that Espresso is waiting for the Root for a while
For this case is suggest just to give up with espresso here and use UiAutomator for this assertion. The Espresso and UiAutomator frameworks could easily work together in one environment.
val device: UiDevice
get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
fun assertPopupIsNotDisplayed() {
device.waitForIdle()
assertFalse(device.hasObject(By.text(yourText))))
}
fun assertPopupIsDisplayed() {
device.waitForIdle()
assertTrue(device.hasObject(By.text(yourText))))
}
Using ActivityScenarioRule and Java
Some imports for the code
import android.view.View;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.not;
1. Declare the rule
//Change YourActivity by the activity you are testing
#Rule
public ActivityScenarioRule<YourActivity> activityRule
= new ActivityScenarioRule<>(YourActivity.class);
2. Initialize the decor view
private View decorView;
#Before
public void loadDecorView() {
activityRule.getScenario().onActivity(
activity -> decorView = activity.getWindow().getDecorView()
);
}
3. Finally test it
#Test
public void testWithToasts() {
//Arrange and act code
//Modify toast_msg to your own string resource
onView(withText(R.string.toast_msg)).
inRoot(RootMatchers.withDecorView(not(decorView)))
.check(matches(isDisplayed()));
}
I'm pretty new to this, but I made a base class 'BaseTest' that has all of my actions (swiping, clicking, etc.) and verifications (checking text views for content, etc.).
protected fun verifyToastMessageWithText(text: String, activityTestRule: ActivityTestRule<*>) {
onView(withText(text)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed()))
}
protected fun verifyToastMessageWithStringResource(id: Int, activityTestRule: ActivityTestRule<*>) {
onView(withText(id)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed()))
}
this works for me
onView(withId(R.id.inputField)).check(matches(withText("Lalala")));
The way Toasts are implemented makes it possible to detect a toast has been displayed. However there is no way to see if a Toast has been requested, thru a call to show()) or to block between the period of time between show() and when the toast has become visible. This is opens up unresolvable timing issues (that you can only address thru sleep & hope).
If you really really want to verify this, here's a not-so-pretty alternative using Mockito and a test spy:
public interface Toaster {
public void showToast(Toast t);
private static class RealToaster {
#Override
public void showToast(Toast t) {
t.show();
}
public static Toaster makeToaster() {
return new RealToaster();
}
}
Then in your test
public void testMyThing() {
Toaster spyToaster = Mockito.spy(Toaster.makeToaster());
getActivity().setToaster(spyToaster);
onView(withId(R.button)).perform(click());
getInstrumentation().runOnMainSync(new Runnable() {
#Override
public void run() {
// must do this on the main thread because the matcher will be interrogating a view...
Mockito.verify(spyToaster).showToast(allOf(withDuration(Toast.LENGTH_SHORT), withView(withText("hello world"));
});
}
// create a matcher that calls getDuration() on the toast object
Matcher<Toast> withDuration(int)
// create a matcher that calls getView() and applies the given view matcher
Matcher<Toast> withView(Matcher<View> viewMatcher)
another answer regarding this
if(someToast == null)
someToast = Toast.makeText(this, "sdfdsf", Toast.LENGTH_LONG);
boolean isShown = someToast.getView().isShown();

Binding to singleton property

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

Change Switch state without animation

In my Android project, I have a ListView with rows containing SwitchCompat items (AppCompat for Switch widget).
My problem occurs when I scroll into the list and getView(...) method of MyAdapter is invoked with a recycled view. I redefine the correct Switch state but the animation is visible.
There is a solution to prevent the animation in this case?
Call jumpDrawablesToCurrentState() to skip the animation
switchCompat.setChecked(true);
switchCompat.jumpDrawablesToCurrentState();
I finally found a solution but seems not really clean:
ViewGroup viewGroup = (ViewGroup) view; // the recycled view
viewGroup.removeView(switch);
switch.setChecked(states[index]);
viewGroup.addView(switch);
If a better solution exists, please share it.
The issue in with animation playing in the list can be present if you use Android Databinding.
To resolve it, run binding.executePendingBindings() method after you set data – it will refresh binding state for the component in current frame and will not wait for the next one to come.
As you have probably guessed already – next frame is the animation
I had the same problem and I managed to solved it using some minimal reflection.
Usage:
To change the switch state without animation call the setChecked(boolean checked, boolean animate) method with false for the animate parameter. If the switch already is animating at the moment this method is being called the animation will be stopped and the switch jumps to the desired position.
SwitchCompatFix.java
import android.content.Context;
import android.support.v7.widget.SwitchCompat;
import android.util.AttributeSet;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Work around for: http://stackoverflow.com/questions/27139262/change-switch-state-without-animation
* Possible fix for bug 101107: https://code.google.com/p/android/issues/detail?id=101107
*
* Version 0.2
* #author Rolf Smit
*/
public class SwitchCompatFix extends SwitchCompat {
public SwitchCompatFix(Context context) {
super(context);
initHack();
}
public SwitchCompatFix(Context context, AttributeSet attrs) {
super(context, attrs);
initHack();
}
public SwitchCompatFix(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHack();
}
private Method methodCancelPositionAnimator = null;
private Method methodSetThumbPosition = null;
private void initHack(){
try {
methodCancelPositionAnimator = SwitchCompat.class.getDeclaredMethod("cancelPositionAnimator");
methodSetThumbPosition = SwitchCompat.class.getDeclaredMethod("setThumbPosition", float.class);
methodCancelPositionAnimator.setAccessible(true);
methodSetThumbPosition.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void setChecked(boolean checked, boolean animate){
// Java does not support super.super.xxx calls, a call to the SwitchCompat default setChecked method is needed.
super.setChecked(checked);
if(!animate) {
// See original SwitchCompat source:
// Calling the super method may result in setChecked() getting called
// recursively with a different value, so load the REAL value...
checked = isChecked();
// Cancel any running animations (started by super.setChecked()) and immediately move the thumb to the new position
try {
if(methodCancelPositionAnimator != null && methodSetThumbPosition != null) {
methodCancelPositionAnimator.invoke(this);
methodSetThumbPosition.invoke(this, checked ? 1 : 0);
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
Note for proguard users:
Because this method uses reflection an additional proguard rule might be needed (if not yet present).
-keep class android.support.v7.widget.SwitchCompat {
private void cancelPositionAnimator();
private void setThumbPosition(float);
}
This additional rule is not needed when you're using one of the following proguard rules (or similar ones):
-keep class android.support.v7.widget.** { *; }
-keep class android.support.v7.** { *; }
Using SwitchCompat and DataBinding
#BindingAdapter({"bind:checkedState"})
public static void setCheckedState(SwitchCompat switchView, boolean checked) {
int visibility = switchView.getVisibility();
switchView.setVisibility(View.INVISIBLE);
switchView.setChecked(checked);
switchView.setVisibility(visibility);
}
Then in xml:
<android.support.v7.widget.SwitchCompat
android:id="#+id/my_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:checkedState="#{my_data.checked}"/>
And don't forget to call executePendingBindings() (thanks AAverin)
For Kotlin developer:
fun SwitchCompat.setCheckedWithoutAnimation(checked: Boolean) {
val beforeVisibility = visibility
visibility = View.INVISIBLE
isChecked = checked
visibility = beforeVisibility
}
And the usage:
mySwitch.setCheckedWithoutAnimation(true)
Kotlin sample of Rolf ツ answer.
class SwitchImproved(context: Context, attributeSet: AttributeSet) : SwitchCompat(context, attributeSet) {
private lateinit var methodCancelPositionAnimator: Method
private lateinit var methodSetThumbPosition: Method
init {
initHack()
}
fun setChecked(checked: Boolean, animate: Boolean = true) {
super.setChecked(checked)
if (!animate) {
methodCancelPositionAnimator.invoke(this)
methodSetThumbPosition.invoke(this, if (isChecked) 1 else 0)
}
}
private fun initHack() {
methodCancelPositionAnimator = SwitchCompat::class.java.getDeclaredMethod("cancelPositionAnimator")
methodSetThumbPosition = SwitchCompat::class.java.getDeclaredMethod("setThumbPosition", Float::class.javaPrimitiveType)
methodCancelPositionAnimator.isAccessible = true
methodSetThumbPosition.isAccessible = true
}
}
In my case, I am using the new material library:
implementation 'com.google.android.material:material:1.1.0-alpha07'
and in the setChecked method of this class there is this condition:
if (getWindowToken() != null && ViewCompat.isLaidOut(this))
So what I did was to create a class that extends from this SwitchMaterial, and deal with "isLaidOut". The code is the next one (omitting constructors):
class SwitchCustomView : SwitchMaterial {
private var laidOutForAnimation = false
fun setChecked(checked: Boolean, animate: Boolean) {
if (!animate) {
laidOutForAnimation = true
}
super.setChecked(checked)
laidOutForAnimation = false
}
override fun isLaidOut(): Boolean {
return if (laidOutForAnimation) {
return false
} else {
super.isLaidOut()
}
}
}
Then just use this class in your xml and call programatically
setChecked(checked: Boolean, animate: Boolean)

Categories

Resources