xamarin

Pisanie kodu w Xamarin Studio (na Macu) zamiast VS

Posted on Updated on

Thinks to keep in mind when using Xamarin Studio on Mac:
– Change formatting to VS: Preferences->Source Code->Code Formatting->C# source code->Policy->MS Visual Studio
– Do not indent new lambda brackets (indent blocks inside expressions) Preferences->Text Editor->Behavior->Indentation->Indentation mode->Automatic

Turn of asking about converting End Of Lines:
Tools->Options->General->Line ending conversion->Leave line ending as is

Advertisements

Xamarin: uruchamianie kodu UI, własny Dispatcher

Posted on Updated on

Xaramin Forms sprawia wiele problemów i chciałem opisać jeden z nich. To wszystko kiedyś oczywiście może zacząć działać lepiej, niemniej obecnie (styczeń 2016) są problemy.

Aplikacja z którą pracuję jest obecna dwóch platformach Android oraz iOS.

UiThread, UiContext, wątek UI, – nieistotne nazwa, chodzi o to samo.

Zakładam, że czytelnik wie dlaczego zmiany na UI trzeba wykonywać z wątku z którego ten UI był stworzony. Nie będę też tłumaczył dlaczego długotrwałe operacje nie robimy na wątku UI.

Wspólna metoda Device.BeginInvokeOnMainThread() i kłopoty

Jest wspólna dla obydwu platform metoda Device.BeginInvokeOnMainThread(Action action). Ta metoda powoduje zakolejkowanie akcji do wykonania na UiThread. Metoda zaczyna się od Begin więc nie jest tutaj nic blokowane, zaraz skaczemy do następnej linijki.

Device.BeginInvokeOnMainThread(() =>
{
    IsErrorVisible = false;
});
Foo(); // will be executed right after above line, probably before above action is executed

Z takim kodem na Androidzie są 2 problemy.
– nie jest nawet sprawdzone czy jesteśmy w UiThread, w związku z tym nawet gdy jesteśmy i akcja mogłaby być wykonana synchronicznie od razu to i tak jest wrzucana do kolejki.
– z testów wyszło że czasem akcja rozpoczyna wykonanie 1s później, bez jakichkolwiek wyraźnych przesłanek dlaczego tak późno. Nie wiadomo dlaczego tak jest… (ok, chodzi o jakiś stary mechanizm synchronizacji ze starego .NETa, który jest wykorzystywany przez Mono na Androidzie – generalnie ten mechanizm jest słaby)

Na iOS te problemy nie występują/są mniejsze. Niby fajnie, ale teraz jest problem bo kod wspólny (proste wywołania na UI) wygląda inaczej gdy jest wykonany na różnych urządzeniach.

Rozwiązaniem jest własny Dispatcher

który opakowauje wywołania BeginInvokeOnMainThread i traktuje je inaczej per platforma.

public interface IDispatcher
{
    void ToUi(Action action);
}

Implementacja na Androida

public class AndroidDispatcher : IDispatcher
{
    public void ToUi(Action action)
    {
        if (Looper.MainLooper.Thread == Thread.CurrentThread())
        {
            action();
            return;
        }

        var activity = Forms.Context as Activity;
        if (activity != null)
        {
            activity.RunOnUiThread(action);
        }
    }
}

Najpierw pierwszy if:
Jeśli jesteśmy w wątku UI to po prostu wykonaj synchronicznie akcje. Nie trzeba się bawić w asynchroniczności po prostu wykonujemy co mamy zrobić.

if (Looper.MainLooper.Thread == Thread.CurrentThread())
{
    action();
    return;
}

W drugiej części kodu jest to, co powinno być od razu (w Device.BeginInvokeOnMainThread()) robione dobrze.

var activity = Forms.Context as Activity;
if (activity != null)
{
    activity.RunOnUiThread(action);
}

RunOnUiThread() zgodnie z dokumentacją, wykonuje akcje od razu gdy w UiThread lub wrzuca do kolejki oczekujacej na UiThread. Implementacja natywna po prostu zachowuje się lepiej.

Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.

Implementacja na iOS

Podobnie jak wcześniej sprawdzamy czy jesteśmy w UiThread i wykonujemy akcję od razu. Jeśli nie jesteśmy to korzystamy z natywnej wersji.

public class IosDispatcher : NSObject, IDispatcher
{
    public void ToUi(Action action)
    {
        if (NSThread.Current.IsMainThread)
        {
            action();
        }
        else
        {
            InvokeOnMainThread(action);
        }
    }
}

Implementacja na iOS (Xamarin.Forms)

Wersja Forms ma więcej problemów i tutaj musimy ostatecznie użyć Device.BeginInvokeOnMainThread(). Jego implementacja na iOS jest zdecydowanie lepsza niż na Androida więc odpalenie akcji będzie w miarę szybkie (bez zbędnych opóźnień).

public class IosDispatcher : IDispatcher
{
    public void ToUi(Action action)
    {
        if (NSThread.Current.IsMainThread)
        {
            action();
        }
        else
        {
            Device.BeginInvokeOnMainThread(action);
        }
    }
}

Cała rozkminka dzięki Waldkowi Zubikowi.

Xamarin

Posted on Updated on

Różne:
* będąc na symulatorze możemy zmieniać wartości podczas debugowania (np Intermadiate Window). Na urządzeniu nie możemy tego robić.

Gdy mamy jakieś dane w aplikacji i nie chcemy aby za każdym razem były czyszczone:
Xamarin does not clear previous build and data

Conditional breakpoint jednak działa

Na breakpoincie możemy kliknąć prawym i wybrać “Condition”. Dotychczas myślałem że to nie działa na Xamarinie (bo sprawdziłem i nie działało), ale właśnie mnie kolega oświecił i to działa tylko trzeba to ustawić przed odpaleniem debuggera. Zmiany tego w trakcie nie działają.