piątek, 29 kwietnia 2016

[DSP2016] iOS - ciężkie intro (Swift 2 reedukacja w pigułce, Xcode + hello light organ + github, sprzęt)

Mamy już rozwiązanie na Android i Raspberry Pi, teraz pora rozprawić się z iOS.  Przecież ktoś mający urządzenie z jabłkiem też może chcieć cieszyć się kolorowymi błyskami świateł! Dziś kilka słów wstępu na ten temat.

App-kę będziemy pisać w Swift w Xcode (aby nie było za lekko). Z tej okazji przeszedłem reedukację ze Swift by później lepiej mi się tworzyło (kiedyś poznawałem ten język, ale na sucho, teraz trzeba było sobie przypomnieć  linie harmoniczne się jak muzyk orientalny instrument przed wejściem do studia –Winking smile). Mogę polecić szkolenie Swift in Depth. Oczywiście warto też korzystać z oficjalnej dokumentacji Apple oraz oficjalnego bloga Swift.

Zapiski z mojej powtórki tego języka znajdziecie poniżej:

 

Podstawy podstaw

Metoda w klasie:  func, typ zwracany po strzałce

func f (x: Int) –> String {  … }

tworzenie obiektu klasy: bez słowa new

let a = A()

nie ma średników, chyba że damy dwie komendy po sobie w tej samej linii

if #available(iOS 8.0, *)

if-y bez nawiasów

a = b = c  nielegalne (a = b nie produkuje wartości)

operatory zakresu:    1…3    1..<3     if  1…10 ~= i  { … }

var x = 10   var zmienna, interferencja typu

var x: Int   zmienna o typie

let s = “abc”   let stała

let s: String

s = “def”

Double(1)  - konwersja na Int na Double

String (swift) a NSString (API):     objcStr = swiftStr      ale    swiftStr = objcStr as String

var y: Int?   typ opcjonalny   rozpakowanie:   let x = y!     gdy nie ma wartości Int y ma wartość nil

jeśli użyjemy typu Int! to wystarczy let x = y  (automatyczne wypakowanie)

bezpieczna składnia:   if   let a = optionalValue  {  … }

if   let a = optionalValue,  //jeśli nie nil to przejdź dalej

            b = func1()  where a < b,   //jeśli b nie nil, to sprawdź warunek

            let c = func2()   {    //jeśli warunek ok i c nie nil

     …

}

var array:  [String] = [“a”, “b”, “c”]

var array2 = [String] (count: 3, repeatedValue: “”)  //inicjalizacja powtarzalną wartością

var x = array [1…2]

 

Klasa / struktura/ enum

class C {

var x = 0

var y = 0

//inicjalizator

init(a: Int, b: Int) { … }

//deinicjalizator

deinit { … }

//metoda

func f (d: Int) {

     self.x = d

}

final func g(d: Int) { … }  //nie do nadpisania

lazy var offset = getDefault()  //leniwa inicjalizacja

//statyczne pole

static var k = 2

//statyczna metoda z możliwością nadpisania

class func h(d: Int) { … }

//statyczna metoda nie do nadpisania

static func m(d: Int) { … }

}

let c = C(a: 2, b: 3)

typ opcjonalny C?

struct C {

       //zmienne

       //init

       //nie ma deinit

      //metoda modyfikująca strukturę

       mutating func f (d: Init) {  self.x = d }

       //możliwość wyczyszczenia wszystkiego

      mutating func clear() { self = C() }

}

Opcjonalny łańcuch wywołania (jak w C# 6):  

obj.next?.next  (drugie next wywoła się jeśli pierwsze nie będzie nil)

Krotki (w stylu C#7, TS)

let error = ( 500, “Internal error”)

dostęp:    error.0  error.1

let (status, desc) = error //odpowiednia inicjalizacja status i desc

let (status, _ ) = error //drugie pole ignoruj

let error = (code: 500, desc: “Internal error”) //pola nazwane

error.code   error.desc

Interpolacja stringa (podobnie jak  w C# 6):   “\ (status)”

Enum

enum Direction {

case North

case South

}

var dir = Direction.North

dir = .South  //skrótowy zapis

różne dane:

enum PostalCode {

case US (Int, Int)

case UK(String)

}

var p1 = PostalCode.US(12345, 6789)

switch p {

case .US (let loc, var route): …

case .US (1111, 2222): …

case .US (1..<100, _): …

}

enum ASCIIControls: Character {

case Newline = “\n”

case Tab = “\t”

}

let str = ASCIIControls.Tab.rawValue

enum D {

case DISTANCE ( Int )

init (distance: Int) { self = DISTANCE(distance) }

func value() –> Int {

        switch self  {

               case .DISTANCE (let value):  return value

        }

}

//mutating func też może być

}

 

Kontrola przepływu

for:

for element in list { … }

for i in 0..< list.count { … }

for _ in 1…5 { … }

for ;; { … }  //nieskończenie wiele razy

while typowa

do while:

repeat {

}  while warunek

switch:

let p = (1, 2)

switch p {

case (let x, 2):  …

case let(x, y) where x>0 & y > 0: …

}

var opt: Int? = 10

switch opt {

case let val? where val > 0:  …

case nil:  …

}

//case w if-ie

if case let .UK(val) = p { … }

//case w for  np. tablica Int?

for case let n? in array where n < 3 {  … }

wyjątki:

//nie obiekty, tylko enum-y!

class M {

enum Error: ErrorType { case NoSuchFile(String), NotOpen }

init (_ path: String) throws {

        …  throw Error.NoSuchFile(path)

}

func readLine() throws –> String { 

       …

       throw Error.NotOpen

}

}

let f = try!  M(“abc”) 

//etykieta try zamiast bloku, przy każdej metodzie wyrzucającej wyjątek, try! wiem, ale nic z tym nie robię (tam gdzie wiemy że nie będzie wyjątku)

defer { f.close() }  //wykonanie niezależnie od wszystkiego, coś jak finally

do {

let f = try M(“abc”)

defer {  f.close() }

try f.readLine()

}

catch let M.Error.NoSuchFile(path) where path==”/x” {  … }

guard - wykonanie jeśli nie

guard let x = a, y = b where 0 < x && x < b else { … }

 

Funkcje i klauzule

func f() –> () { … }  gdy nic nie zwracamy (opcjonalny zapis)

func p(first: String, last: String) { … }

p(“abc”, last: “xyz”)  //etykiety w dalszych parametrach podobnie jak w Objective-C

func p(given first: String,  family last: String) { … }  //etykiety zewnętrzne

p(given: “abc”, family: “xyz”)

func p(_ first: String, _ last: String) { … }  //bez nazwanych etykiet

p(“abc”, “xyz”)

różne nazwy etykiet – różne przeładowania funkcji

wartości domyślne atrybutów jak w C#

func sum(numbers: Int…) –> Int { … }     f(1,2,3,4)  //wiele argumentów typu Int

func f( inout a: Int, inout _ b: Int)  { … }  //parametry dwukierunkowe np. zamiana a z b

var l = “L”     var r = “R”   f(&l, &r)

var talk: () –> () = hello  // funkcja hello     talk()   //referencja na funkcję

klauzule (funkcje lambda):

let ref = { (x: Int) –> Double in return Double(x) }

let ref: (Int) –> Double = {  x in return Double(x) }

let ref: (Int) –> Double = {  Double($0) }

names.sort() { $0 < $1 } //można wyciągnąć poza, jeśli jedynym argumentem jest klauzula (trailing closure)

names.sort { $0 < $1 }

{   

      let x = $0 + 1

      return “\(x)”

}

 

Dziedziczenie

class A {

func f() { … }

}

class B: A {

override func f() { … }

}

pole == stored property  

właściwość (get/set) == computed property

class P {

      var firstName = “Marcin”

var fullName:  String {

        set {

           … newValue 

       }

       get {

          return …

      }

}

//read-only

var mail: String {  return … }

}

class E: P {

override var fullName: String { get { … }   set { … } }

//nadpisanie także prostych

override var firstName:  String { set { … }  get { … } }

//obserwatory

override var lastName:  String = “Kapitan”  {

        willSet(newVal) {  … newVal  }

        didSet(oldVal) { … oldVal }

}

}

Inicjalizatory

Może być ich wiele w jednej klasie

class S {

var d: Double

//inicjalizator designated - inicjalizuje wszystkie składowe + wywołuje odpowiedni inicjalizator z klasy bazowej

init (d: Double)  {

       self.d = d

}

//inicjalizator convenience - wywołuje designated lub inny convenience

convenience init(i: Int)  {

      self.init(d: …)

}

}

class SS:  S {

      override init(d: Double) {    //designated pochodnej musi odwoływać się do designated bazowej

            super.init(d: d)

      }

}

convenience w pochodnej nie może odwoływać się do convenience ani designated w bazowej, może tylko do innego convenience / designated w swojej klasie 

jeśli klasa pochodna nie ma inicjalizatorów –> dziedziczy wszystkie designated z bazowej

jeśli klasa pochodna implementuje wszystkie inicjalizatory designated bazowej –> dziedziczy wtedy inicjalizatory convenient

inicjalizator, który może zawieść

struct F {

init?(_ x: String) {

       if x.isEmpty {

              return nil

       }  …

}

}

Inicjalizator wymagający nadpisania w klasie pochodnej:

class A {

required init() { … }

}

class B: A {

required init() { … }

}

 

Dostęp, rzutowania, generyki, rozszerzenia

Dostęp na poziomie plików, nie klas  (public, internal – app/framework/module, private - tylko z tego pliku)

public class A {

private func b() { … }

}

public class B: A {

override public func b() { … }

}

Rzutowanie

if p is S  {

var s:S   =  p as!  S  (as - gwarantowane przez kompilator, as! - wymuszenie, może być błąd w runtime)

}

if let s2 = p as? S  { … }

AnyObject,  Any

case  0.0  as Double:

case is Double:

case let d as Double where d > 0:

Generyki podobnie jak w C#

funkcje, klasy, enum-y

Rozszerzenia

extension String  {

var length: Int { return self.characters.count }

}

extension Double {

var km: Double {  return self * 1_000 }

}

12.3.km

===  czy to ten sam obiekt

 

Pamięć

class C {

var array: [D] = []

}

class D {

weak var myC: C?

}

class D {

unowned var c: C  //ten sam czas życia co C, C nie ma wartości nil

}

 

Protokoły (interfejsy)

protocol R {

func f(a: Int) –> String

init(a: Int, b: String) 

}

class A: R {

func f(a: Int) –> String { … }

required init(a: Int, b: String)  { … }

}

x: protocol<Representable, Cacheable>  // implementuje oba

protocol Cacheable: Representable {  //dziedziczenie protokołów

static var version: Int { get set }

var id: String { get }

mutating func load (data: String)  //dla struktur, w klasie zwykła funkcja

func replaceWith(other: Self)   //Self - klasa implementująca

}

protocol P: class { … }  //implementować go mogą tylko klasy

Nie ma generycznych, ale można to obejść

@objc protocol ObjcProtocol {

func f (x: Int)   //typy argumentów:  proste (np. Int, String), AnyObject (nie Any), enum z ObjC, tablice, słowniki, protokoły, klauzule, operatory…  Nie można typealias, opcjonalnych, klas z Swift, inout, krotek, Self ….

       optional var x: Int { get }

optional func g() –> Int    //wywołanie  obiekt.g?()

class ObjcSubclass:  ObjcProtocol {

@objc func f(x: Int)  {  … }   //nie potrzebne @objc gdy dziedziczymy po NSObject

}

extension Person: TextRepresentable { … }  //extension może implementować interfejs

rozszerzenie protokołu, łatwe dodanie np. implementacji nowej funkcji do wszystkich klas implementujących protokół

 

Zobaczymy jak pójdzie pisanie w praktyce. Najnowszy Xcode 7.3  ma w sobie Swift w wersji 2.2 i taki też założyłem projekt, który można już znaleźć na github. Jak dodałem plik .gitignore?  Zrobiłem podobnie jak w filmiku Adding a .gitignore file to a Swift project in Xcode. Do wbitek używam podobnie jak w Windows programu GitHub Desktop. Aplikacja na razie jeszcze nic nie robi, tylko wyświetla napis “Hello Light Organ”.

WP_20160429_22_40_54_Pro

Najtańszym sposobem na pozyskanie środowiska dla Xcode (poza Hackintoshem czy maszyną wirtualną) jest coś takiego jak Mac Mini, które w wersji 8GB pamięci RAM nie jest może demonem szybkości, ale działa zadawalająco. Generalnie nie ma się co bać OS X. Wystarczy zwykła nawet stara klawiatura i mysz od Windows, monitor z HDMI i… wszystko działa bez problemów.  Warto zaktualizować sobie system do najnowszej wersji, aktualnie jest to El Capitan 10.11.4. Wszelkie takie przesądy w stylu, że “nie wiadomo co i jak” można między bajki włożyć.

wtorek, 26 kwietnia 2016

BUILD 2016 - odc.4 (XAML, Xamarin, Continuum, IoT, Keynote 2)

W DSP ruszyłem z kolejnymi nowymi rzeczami… ale dziś nie napiszę jeszcze o nich, tylko zrobimy sobie małą odskocznię do BUILD’a 2016, a konkretnie do kolejnych 5 wystąpień z tego eventu.

Kilka uwag, jakie mi się zebrały:

  • XAML - w zasadzie nic już nowego w porównaniu do innych wystąpień z BUILD 2016 (no może prezentowanie ustawień z Automation UI)
  • Xamarin - na wideo z playbacku raczej nic nowego, na Keynote emulator iOS uruchamiany zdalnie w Windows (!), a także “samouczek na żywo” (dzięki Roslyn)
  • Continuum - kilka sympatycznych ficzerów, niektóre po raz pierwszy raz pokazane
  • IoT - spodziewałem się trochę więcej pokazów, jakiegoś WOW, prezentacja mnie nie rozwaliła, ale dostajemy trochę konkretnych informacji co w tej dziedzinie jest robione i co trzeba jeszcze zrobić
  • KeyNote 2 - dobry pokaz Xamarin z kilkoma nowościami, reszta jakoś mniej już przykuła moją uwagę (czasami wydała się hm… wtórna), choć warto z pewnością poznać Azure & IoT

 

Runtime Editing Tools for XAML

image

propercje z AutomationPeer

 

Native iOS, Android, & Windows Apps from C# and XAML with Xamarin.Forms

image

image

image

 

Continuum for Phone

image

image

multitasking

image

Visual Studio na telefonie dzięki Azure Remote App

image

projekcja na Surface

image

zdalne sterowanie przez telefon jako … touchpadem

image

granie w gry - klawiatura, kontroler XBox’a

image

odczyt na telefonie plików z pendrive’a podłączonego do peryferiów

 

Windows in the Smart Home: The Internet of Things and UWP

image

image

image

image

w tym roku łatwiejsza obsługa niż rok temu

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

 

Keynote Presentation

image

Xamarin: emulator iOS uruchamiany ZDALNIE na WINDOWS!

image

żywa dokumentacja

image

testowanie

image

image

poniedziałek, 25 kwietnia 2016

[DSP2016] Android prosto z poligonu odc.10 (ustawienia, sockety) + Raspberry Pi

W tym odcinku impreza na biurku na całego! Mamy działającą komunikację po socketach pomiędzy app-ką Android, a ostatnio prezentowanym sprzętowym rozwiązaniem opartym na Raspberry Pi 2 i Windows 10 IoT Core. Zresztą sami zobaczcie:

Aby dość wygodnie korzystało się z takiej komunikacji, w aplikacji Android wprowadziłem obsługę ustawień:  czy wysyłamy dane sterujące na zewnątrz, adres hosta i numer jego portu:

Screenshot_20160424-030350    Screenshot_20160424-030241 

Screenshot_20160424-030451    Screenshot_20160424-030510 

Ustawienia w Android to całkiem dość bogate zagadnienie. Na początek mogę polecić dwa oficjalne linki:

Dodatkowo “na boku” testowo wygenerowałem sobie w Android Studio aktywność z ustawieniami (New –> Activity –> Settings Activity), by zobaczyć co dostajemy w takiej żywej próbce kodu od Google. Przykład niezły, ale zbyt mocno złożony w stosunku do tego, co potrzebowałem, więc funkcjonalność ustawień pisałem ręcznie kierując się powyższymi linkami, natomiast z tej przykładowej implementacji wyciągnąłem tylko kilka drobnych patentów.

Zgodnie z zaleceniami postawiłem na swoją dowolną aktywność SettingsActivity, która gości w sobie specyficzny fragment prezentujący ustawienia u mnie pod nazwą SettingsFragment. Kod aktywności wygląda następująco:

public class SettingsActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setupActionBar();

getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
}

private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
startActivity(new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}

Jak widzimy nie ma ona layoutu w XML. Aby narysował się pasek aplikacji w manifeście nie ustawiłem dla niej skórki AppTheme.NoActionBar, tak jak mamy zrobione to przy innych aktywnościach. Metoda setDisplayHomeAsUpEnabled powoduje pojawienie się strzałki powrotu, którą zresztą jawnie oprogramowałem w metodzie onOptionsItemSelected.

Przejdźmy teraz do SettingsFragment. Dziedziczymy go po klasie PreferenceFragment i dostarczamy mu plik XML o specjalnej strukturze i elementach, będący w pliku res/xml/preferences.xml. Kod fragmentu wygląda tak:

public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);

SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
onSharedPreferenceChanged(sharedPreferences, PreferencesHelper.KEY_PREF_REMOTE_DEVICE_HOST);
onSharedPreferenceChanged(sharedPreferences, PreferencesHelper.KEY_PREF_REMOTE_DEVICE_PORT);
}

@Override
public void onResume() {
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}

@Override
public void onPause() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

if (key.equals(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_HOST)) {
Preference hostPref = findPreference(key);

String value = sharedPreferences.getString(key, "");

if (TextUtils.isEmpty(value))
hostPref.setSummary(R.string.pref_no_data);
else
hostPref.setSummary(value);
}
else if (key.equals(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_PORT)) {
Preference portPref = findPreference(key);

int value = sharedPreferences.getInt(key, 0);

portPref.setSummary(Integer.toString(value));
}
}
}

Należy się teraz kilka słów wyjaśnienia. Ładowanie we fragmencie interfejsu XML odbywa się w onCreate za pomocą specjalnej metody addPreferencesFromResource. Odczyt ustawień w postaci słownika klucz-wartość możemy dokonać z każdego miejsca aplikacji za pomocą wywołania getPreferenceManager().getSharedPreferences(). Klasa PreferencesHelper to moja klasa trzymająca w sobie stałe typu String do kluczy naszych trzech ustawień (przyda się nam później jeszcze w MusicService). Nasłuchiwanie zmian w ustawieniach aplikacji dokonujemy przez implementację interfejsu SharedPreferences.OnSharedPreferenceChangeListener złożoną z jednej metody onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key). Listenerem jest tutaj cały fragment. Nasłuch zapinamy w metodzie onResume, a odpinamy w onPause.

Po co to wszystko robimy? Co robi dalsza część kodu onCreate po addPreferencesFromResource ? Na te pytania odpowiem, jak zapoznamy się trochę z plikiem XML odpowiedzialnym za narysowanie UI do ustawień. Jego zawartość jest następująca:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

<SwitchPreference
android:key="pref_use_remote_device"
android:defaultValue="false"
android:title="@string/pref_title_use_remote_device"
android:summary="@string/pref_summary_use_remote_device" />

<EditTextPreference
android:key="pref_remote_device_host"
android:dependency="pref_use_remote_device"
android:defaultValue=""
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
android:title="@string/pref_title_remote_device_host" />

<com.apps.kruszyn.lightorganapp.ui.NumberPickerPreference
android:key="pref_remote_device_port"
android:dependency="pref_use_remote_device"
android:defaultValue="8181"
android:title="@string/pref_title_remote_device_port" />

</PreferenceScreen>

Pliki tego rodzaju składają się z elementów *Preference. Mamy kilka predefiniowanych rodzajów (tekst, lista, switch, checkbox), możemy też pisać własne (np. do liczb, co jeszcze omówimy) czy wywoływać z nich własne aktywności. Każde ustawienie z reguły zawiera atrybuty key (klucz) i title (nazwa ustawienia np. “Host”). Gdy nazwa ustawienia nie jest dość jasna, możemy dodać bardziej precyzyjne objaśnienie przez atrybut summary (u mnie przy switch). Atrybut ten używamy też w ustawianiach innych niż switch czy check do opisania aktualnego stanu (u mnie host i port). Domyślną wartość wyrażamy przez atrybut defaultValue. Domyślne wartości dla ustawień najczęściej ładujemy tylko raz przy pierwszym uruchomieniu aplikacji (chyba że chcemy obsłużyć reset do domyślnych). Robimy to w onCreate każdej aktywności, przez którą może użytkownik wejść do aplikacji. U mnie w MainActivity we wspomnianym miejscu znajduje się linijka:

PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

Drugi parametr false oznacza, żeby ładować domyślne ustawienia jeśli nigdy wcześniej nie były już ładowane. Wartością true spowodowalibyśmy ciągły reset ustawień do wartości domyślnych. Patrząc na dwa pierwsze screeny dostrzegamy, że ustawienia hosta i portu nie są aktywne, gdy switch w pierwszym ustawieniu jest w pozycji off. Osiągamy to całkowicie w sposób deklaratywny za pomocą atrybutu dependency.

Wrócmy teraz do kodu fragmentu i odpowiedzi na postawione ostatnio pytania. Po co nasłuch na zmiany w ustawieniach?  Po to, by odświeżać stany ustawień host i port bezpośrednio po zakończeniu ich edycji (atrybuty summary). Metodą findPreference znajdujemy komponent wizualny dla danego ustawienia. Co robimy w onCreate, jak już wczytamy plik XML?  Ustawiamy aktualne stany ustawień host i port, by na dzień dobry było wiadomo, na czym stoimy.

Przyjrzyjmy się edycji ustawień. O ile do wartości tekstowej dostajemy standardowy EditTextPreference, o tyle w przypadku liczby potrzebujemy napisać sobie sami taki komponent. U mnie jest to klasa NumberPickerPreference o kodzie:

public class NumberPickerPreference extends DialogPreference {

private static final int DEFAULT_VALUE = 0;

private NumberPicker mNumberPicker;
private Integer mCurrentValue;
    public NumberPickerPreference(Context context, AttributeSet attrs) {
super(context, attrs);

setDialogLayoutResource(R.layout.numberpicker_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);

setDialogIcon(null);
}

@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);

mNumberPicker = (NumberPicker)view.findViewById(R.id.numberPicker);
mNumberPicker.setMinValue(0);
mNumberPicker.setMaxValue(65535);

if (mCurrentValue != null)
mNumberPicker.setValue(mCurrentValue);
}

@Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
mCurrentValue = mNumberPicker.getValue();
persistInt(mCurrentValue);
}
}

@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
} else {
mCurrentValue = (Integer) defaultValue;
persistInt(mCurrentValue);
}
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInteger(index, DEFAULT_VALUE);
}


@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();

if (isPersistent()) {
return superState;
}

final SavedState myState = new SavedState(superState);
myState.value = mNumberPicker.getValue();
return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {

if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
return;
}

SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());

mNumberPicker.setValue(myState.value);
}
    private static class SavedState extends BaseSavedState {

int value;

public SavedState(Parcelable superState) {
super(superState);
}

public SavedState(Parcel source) {
super(source);
value = source.readInt();
}

@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(value);
}

public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {

public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

i layoucie numberpicker_dialog.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">

<NumberPicker
android:id="@+id/numberPicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

Do zrozumienia tej implementacji polecam link http://developer.android.com/guide/topics/ui/settings.html#Custom. Przy praktycznej finalizacji posiłkowałem się też całkiem dobrymi przykładami na stronach https://www.staldal.nu/tech/2015/05/16/custom-preference-for-android/ i http://codetheory.in/saving-user-settings-with-android-preferences/ (w tym drugim przy okazji dobre podsumowanie wszystkich zagadnień związanych z ustawieniami).

Teraz pora na sockety w serwisie  muzycznym w tle, powiązane z ustawieniami aplikacji. Zasadnicze zmiany w MusicService.java kształtują się w następujący sposób:

private Socket mCommandSocket;
private OutputStream mOutputStream;

private PreferenceListener mPrefListener;
@Override
public int onStartCommand(Intent startIntent, int flags, int startId) {

Context context = getApplicationContext();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);

boolean useRemoteDevice = preferences.getBoolean(PreferencesHelper.KEY_PREF_USE_REMOTE_DEVICE, false);

if (useRemoteDevice)
createNewSocket(preferences);

mPrefListener = new PreferenceListener();
preferences.registerOnSharedPreferenceChangeListener(mPrefListener);

}
@Override
public void onDestroy() {
releaseSocket();


}
@Override
public void onLightOrganDataUpdated(LightOrganData data) {


byte bassValue = (byte)Math.round(255 * data.bassLevel);
byte midValue = (byte)Math.round(255 * data.midLevel);
byte trebleValue = (byte)Math.round(255 * data.trebleLevel);

byte[] bytes = new byte[3];
bytes[0] = bassValue;
bytes[1] = midValue;
bytes[2] = trebleValue;

sendCommand(bytes);
}
private void sendCommand(byte[] bytes) {
try {

if (mOutputStream != null) {
mOutputStream.write(bytes);
mOutputStream.flush();
}

} catch (IOException e) {
e.printStackTrace();
}
}

private void createNewSocket(SharedPreferences preferences) {

String host = preferences.getString(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_HOST, "");
int port = preferences.getInt(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_PORT, 0);

if (!TextUtils.isEmpty(host) && port > 0) {
Runnable connect = new ConnectSocket(host,port);
new Thread(connect).start();
}
}

private void releaseSocket() {
try {
byte[] bytes = new byte[3];
bytes[0] = 13;
bytes[1] = 13;
bytes[2] = 13;

sendCommand(bytes);

if (mOutputStream != null)
mOutputStream.close();

if (mCommandSocket != null)
mCommandSocket.close();

} catch (IOException e) {
e.printStackTrace();
}
mOutputStream = null;
mCommandSocket = null;
}

private void onPreferenceChanged(SharedPreferences sharedPreferences, String key) {

try {

boolean useRemoteDevice = sharedPreferences.getBoolean(PreferencesHelper.KEY_PREF_USE_REMOTE_DEVICE, false);

if (key.equals(PreferencesHelper.KEY_PREF_USE_REMOTE_DEVICE)) {
if (mCommandSocket != null && !useRemoteDevice) {
releaseSocket();
} else if (mCommandSocket == null && useRemoteDevice) {
createNewSocket(sharedPreferences);
}
} else if (key.equals(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_HOST) || key.equals(PreferencesHelper.KEY_PREF_REMOTE_DEVICE_PORT)) {
if (useRemoteDevice) {
releaseSocket();
createNewSocket(sharedPreferences);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public class ConnectSocket implements Runnable {

private String mHost;
private int mPort;

public ConnectSocket(String host, int port) {
mHost = host;
mPort = port;
}

@Override
public void run() {

try {

mCommandSocket = new Socket(mHost, mPort);
mOutputStream = mCommandSocket.getOutputStream();

} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class PreferenceListener implements SharedPreferences.OnSharedPreferenceChangeListener {

public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
onPreferenceChanged(sharedPreferences, key);
}
}

Komunikacja po socketach jest dość podstawowa. Tworzę socket i zapisuję trójki bajtów charakteryzujące muzykę w danej chwili do strumienia. Podobnie jak we fragmencie nasłuchuje zmian w ustawieniach aplikacji za pomocą interfejsu SharedPreferences.OnSharedPreferenceChangeListener (tutaj osobna klasa listenera). W zależności od zmian likwiduję socket,  tworzę go albo likwiduję i tworzę z innymi parametrami.  Kilka linków, które nieco pomogły ukształtować mi wizję:

To tyle na dziś. Następnym razem zupełna zmiana klimatów.

piątek, 22 kwietnia 2016

[DSP2016] Raspberry Pi & Windows 10 IoT Core

Android Androidem, ale jak chcemy mieć kolorowe migające światła to trudno by ekran telefonu uznać za szczyt możliwości. W tym odcinku zbudujemy rozwiązanie na mikrokomputerze Raspberry Pi 2, które następnie zintegrujemy z naszą uprzednio napisaną app-ką.

Jeśli chodzi o IoT czy Raspberry Pi to w ramach DSP będzie to interesujący epizod, w stosunku do wszystkich planów na techniczne eksperymenty do końca maja, jakie siedzą w mojej głowie –Winking smile 

Co to jest Raspberry Pi? Jak pisać na niego uniwersalne aplikacje Windows 10?  Do kupna i praktycznej zabawy z Raspberry Pi 2 zachęcił mnie w zeszłym roku kurs Tomasza Kopacza Raspberry PI 2 i Windows 10 IoT Core – jak zacząć (fanem Windows 10 we wszystkich odmianach byłem od pierwszych buildów, ale brakowało mi takiego praktycznego wejścia w IoT). Swoje eksperymenty mające na celu przyswojenie pewnych podstaw z tej tematyki zawarłem w cyklu postów Raspberry Pi 2 & Windows 10 IoT Core, który jest dostępny na tym blogu np. w ramach #Windows 10 IoT. Teraz mamy kwiecień 2016 roku i jest już w sprzedaży Raspberry Pi 3 z wbudowanym Bluetooth i Wi-Fi, z ładnym wsparciem także przez Windows 10 IoT Core. Może kiedyś w przyszłości nabędę i tę płytkę, ale na razie sprzętowy szkic oświetlenia oprzemy na wersji drugiej (Raspberry Pi nadal oferowane jest różnych wersjach, a Windows 10 Iot wspiera zarówno wersję drugą, jak i trzecią, która jest kompatybilna z poprzedniczką).

Sprzętowy szkic oświetlenia? Tak, w ramach epizodu szkic dla zrobienia sobie małej dyskoteki na biurku!  Oczywiście bardziej profesjonalna realizacja sprzętowego projektu obejmowałaby listwy czy też panele LED sterowane przy pomocy tranzystorów, ale z uwagi na ograniczony czas w DSP zostawię sobie to ewentualnie na dalszą przyszłość, ponieważ na dniach chcę mocno wejść w iOS, a to być może nie koniec. Na obecny moment zadowolę się płytką prototypową z trzema diodami LED wpiętymi przez oporniki 100 omów do trzech wybranych pinów GPIO z napieciem +5V (czerwona – GPIO 5, żółta – GPIO 6, zielona – GPIO 13) oraz (oczywiście) masy. Całość przedstawia poniższy schemat, ktory wykonałem w popularnym do tego rodzaju zastosowań darmowym programie Fritzing.

light_organ

Na żywo wygląda to tak:

WP_20160422_00_25_24_Pro

WP_20160422_00_26_39_Pro

Cześć sprzętowa to jedno, ale jak sprawić by diody świeciły z różną jasnością i jak przekazywać dane z mobilnej aplikacji do Raspberry Pi? Otóż napisałem sobie uniwersalną aplikację, którą wgrałem na płytkę. Aplikacja ta nasłuchuje na sokecie przychodzących do niej komend w postaci ciągów trzech bajtów i steruje jasnością diód przy pomocy PWM (co to takiego? pisałem kiedyś o tym w postach Raspberry Pi 2 & Windows 10 IoT Core - odc.6- PWM (servo) oraz Raspberry Pi 2 & Windows 10 IoT Core - odc.7- PWM (LED RGB)). Nasłuch zrealizowany jest w klasie SocketServer:

public class SocketServer: IDisposable
    {
        private StreamSocketListener tcpListener;
        private const string port = "8181";

        public event EventHandler<MessageSentEventArgs> NewMessageReady;

        public async void StartListener()
        {
            tcpListener = new StreamSocketListener();
            tcpListener.ConnectionReceived += TcpListener_ConnectionReceived;
            await tcpListener.BindServiceNameAsync(port);
        }

        private async void TcpListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
        {
            var streamSocket = args.Socket;
            var reader = new DataReader(streamSocket.InputStream);
            try
            {
                while (true)
                {
                    uint bytesLoaded = await reader.LoadAsync(3);
                    if (bytesLoaded != 3)
                    {
                        return;
                    }

                    var bytes = new byte[3];
                    reader.ReadBytes(bytes);

                    NewMessageReady?.Invoke(this, new MessageSentEventArgs { Bytes = bytes });
                }
            }
            catch
            {               
            }
        }       

        public void Dispose()
        {
            if (tcpListener != null)
                tcpListener.Dispose();
        }
    }

    public class MessageSentEventArgs : EventArgs
    {       
        public byte[] Bytes;
    }

}

Klasa LightsController odpowiada za sterowanie światłem:

public class LightsController: IDisposable
    {
        private const int REDLED_PIN = 5;
        private const int ORANGELED_PIN = 6;
        private const int BLUELED_PIN = 13;

        PwmPin redPin;
        PwmPin orangePin;
        PwmPin bluePin;

        PwmController pwmController;

        public async Task InitAsync()
        {
            pwmController = (await PwmController.GetControllersAsync(PwmSoftware.PwmProviderSoftware.GetPwmProvider()))[0];
            pwmController.SetDesiredFrequency(100);

            redPin = InitPin(REDLED_PIN);
            orangePin = InitPin(ORANGELED_PIN);
            bluePin = InitPin(BLUELED_PIN);

            SetCyclePercentage(redPin, 1);
            SetCyclePercentage(orangePin, 1);
            SetCyclePercentage(bluePin, 1);

            Task.Delay(50).Wait();

            SetCyclePercentage(redPin, 0);
            SetCyclePercentage(orangePin, 0);
            SetCyclePercentage(bluePin, 0);
        }

        public void SetValues(double bassValue, double midValue, double trebleValue)
        {
            SetCyclePercentage(redPin, bassValue);
            SetCyclePercentage(orangePin, midValue);
            SetCyclePercentage(bluePin, trebleValue);
        }

        public void Stop()
        {
            StopPin(redPin);
            StopPin(orangePin);
            StopPin(bluePin);
        }

        private PwmPin InitPin(int pinNumber)
        {
            var pin = pwmController.OpenPin(pinNumber);           
            pin.Start();          

            return pin;
        }

        private void SetCyclePercentage(PwmPin pin, double value)
        { 
            if (value >= 0 && value <=1)         
                pin.SetActiveDutyCyclePercentage(value);
        }

        private void StopPin(PwmPin pin)
        {
            if (pin.IsStarted)
                pin.Stop();
        }

        private void DisposePin(PwmPin pin)
        {
            if (pin != null)
            {
                StopPin(pin);
                pin.Dispose();
                pin = null;
            }
        }

        public void Dispose()
        {
            DisposePin(redPin);
            DisposePin(orangePin);
            DisposePin(bluePin);
        }
    }

Należy tu skomentować dwie sprawy. Pierwsza, PwmController jest od jakiegoś czasu w Windows Api, ale domyślna instancja okazuje się być null-em, jeśli nie mamy jakiejś sprzętowej kompozycji. Możemy jednak zarejestrować sobie softwarowy PWM provider dostarczany przez Microsoft na github. Druga sprawa to kwestia dobrego zainicjowania jasności świecenia diód. Otóż nie ma lekko, po ustawieniu zera procentowego sygnału i starcie “pinu” dioda … świeci i to maksymalnie, zamiast być kompletnie zgaszona, a potem nie reaguje dobrze na przychodzące komendy dotyczące stopnia jasności. Chodzi o to by zero cyklu oznaczało całkowite zgaśnięcie, a 1 maksymalne świecenie. Wzorując się na patencie z https://github.com/jmservera/RgbLed po “wystartowaniu” pin-ów najpierw je zapalam maksymalnie, po czym po 50 ms je zupełnie gaszę. W efekcie przy uruchamianiu aplikacji widzimy króciutki błysk diód zanim przejdą w stan gotowości do sterowania jasnością.

Całość app-ki spięta jest w MainPage.xaml.cs.  Nie umieszczałem serwera socketów w tle. Do prostego celu jaki tu omawiamy jest to wystarczające:

public sealed partial class MainPage : Page
    {
        private LightsController _controller;
        private SocketServer _socketServer;

        private bool _isIoT;

        public MainPage()
        {
            this.InitializeComponent();

            _isIoT = IsIoT();
        }

        private bool IsIoT()
        {
            return AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.IoT";
        }       

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (_isIoT)
            {
                _controller = new LightsController();
                await _controller.InitAsync();

                _socketServer = new SocketServer();
                _socketServer.StartListener();
                _socketServer.NewMessageReady += SendCommand;
            }          
        }       

        private void SendCommand(object sender, MessageSentEventArgs e)
        {
            if (e.Bytes == null || e.Bytes.Length != 3)
                return;

            var bassValue = e.Bytes[0] / 255d;
            var midValue = e.Bytes[1] / 255d;
            var trebleValue = e.Bytes[2] / 255d;

            _controller.SetValues(bassValue, midValue, trebleValue);
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            if (_isIoT)
            {
                if (_socketServer != null)
                {
                    _socketServer.NewMessageReady -= SendCommand;
                    _socketServer.Dispose();
                    _socketServer = null;
                }

                if (_controller != null)
                {
                    _controller.Dispose();
                    _controller = null;
                }
            }

            base.OnNavigatingFrom(e);
        }
    }

Dla przypomnienia aplikacja jest uniwersalna, można ją zdeploypować także na inne odmiany Windows 10 (np. desktop). Aktualnie jej prawie cały kod działa z użyciem API z Windows IoT Extension (może kiedyś w przyszłości rozbuduję ją do odpowiednika znanego z Androida). Jeśli jednak ktoś uruchomiłby ją na czymś innym, to aby nie poszedł na dzień dobry wyjątek używam zmiennej _isIoT.

Aha, oczywiście app-ka musi mieć w manifeście nadane uprawnienia internetClient, internetClientServer i privateNetworkClientServer. Bycie serwerem w sieci w Windows 10 IoT jest możliwe i odpowiednie do poruszanej przez nas problematyki (urządzenia IoT mają małą wydajność, więc nie jest to oczywiście najlepszy pomysł na serwer w ogóle, zwłaszcza w Internecie czy gdy nie panujemy nad ruchem sieciowym).

LightOrganTestApp

Napisałem sobie też drugą testową aplikację (także uniwersalną, choć nie ma to tutaj dużego znaczenia), która na wskazany adres (i port zaszyty w kodzie) wysyła odpowiedni ciąg trzech bajtów tworzony na podstawie danych wpisywanych do okna (od 0 do 255). Jej zasadniczy kod znajdziemy w MainPage.xaml.cs:

public sealed partial class MainPage : Page
    {
        private StreamSocket _streamSocket;
        private static DataWriter _writer;

        public MainPage()
        {
            this.InitializeComponent();
        }       

        private async void Connect(string serverIP, string serverPort)
        {
            try
            {
                var hostName = new HostName(serverIP);
                _streamSocket = new StreamSocket();
                await _streamSocket.ConnectAsync(hostName, serverPort);
                _writer = new DataWriter(_streamSocket.OutputStream);

                var dialog = new MessageDialog("Connection OK");
                await dialog.ShowAsync();
            }
            catch
            {
                var dialog = new MessageDialog("Connection error");
                await dialog.ShowAsync();
            }
        }

        private async void SendBytes(byte[] bytes)
        {
            _writer.WriteBytes(bytes);
            await _writer.StoreAsync();
        }

        private void connectBtn_Click(object sender, RoutedEventArgs e)
        {
            Connect(hostTxt.Text, "8181");
        }

        private void test1Btn_Click(object sender, RoutedEventArgs e)
        {
            byte bassValue = Convert.ToByte(bassTxt.Text);
            byte midValue = Convert.ToByte(midTxt.Text);
            byte trebleValue = Convert.ToByte(trebleTxt.Text);
          
            SendBytes(new byte[] { bassValue, midValue, trebleValue });
        }       
    }

Aplikacja testowa to oczywiście szybki prototyp, nie kładę pary w jego dopracowanie (np. zabezpieczenie pól tesktowych przed wpisywaniem złych wartości czy nawet disablowanie kontrolek przy braku połączenia), ponieważ w najbliższym czasie skupię się już na docelowej integracji w Androidzie. Ta app-ka jest przeznaczona tylko do początkowych wewnętrznych testów.

Źródła powyższych obu app-ek są na github.

Na koniec kilka pytań. Czy komunikacja przez zwykłe czyste sokety była jedyną możliwością? Nie. W grę wchodziło choćby nawiazanie zdalnej sesji SSH (na pewno Raspbian, nie wiem jak szczegóły wyglądają w przypadku Windows 10) czy popularny dla gadżetów Bluetooth (nie mam na ten moment stosownego rozszerzenia do Raspberry Pi 2, może wrócę kiedyś do tematu przy Raspberry Pi 3?). W tematyce sieciowej, której tutaj nie zgłębiałem jest jeszcze kwestia stawiania sieci Wi-Fi przez samo Raspberry Pi. Nie jest to jednak konieczne, jeśli mamy już inną sieć Wi-Fi (a tak zwykle jest).

poniedziałek, 18 kwietnia 2016

[DSP2016] Android prosto z poligonu odc.9 (językowa lokalizacja, ikona aplikacji)

Dziś odcinek wagi lżejszej. Zajmiemy się drobnymi, ale niezbędnymi sprawami.

Instalując app-kę na urządzeniu fajnie byłoby, aby jej nazwa oraz zawarte w niej napisy były zgodne z aktualnie ustawionym językiem w systemie. Na stronie  http://developer.android.com/training/basics/supporting-devices/languages.html znajdziemy wyczerpujące informacje  na ten temat. W źródłach wprowadziłem:

  • plik res/values-pl/strings.xml - polskie tłumaczenia domyślnych tekstowych zasobów w języku angielskim (res/values/strings.xml)
  • w kilku plikach *.java zmiany statycznych stringów na wartości tekstowych zasobów

W ramach tego drugiego punktu posłużyłem się dwiema wersjami kodu ładującego zasoby. Tam, gdzie było to możliwe podałem bezpośredni identyfikator (np. R.string.not_all_permissions_msg), tam gdzie potrzebny był koniecznie łańcuch znaków skorzystałem z postaci:

getResources().getString(R.string.search_songs)

W przypadku zasobów z parametrem definujemy je zgodnie z dokumentacją na stronie http://developer.android.com/guide/topics/resources/string-resource.html. Przykładowo u mnie wystąpiła fraza:

Uprawnienie %1$s nie zostało nadane.

W miejsce %1$s może zostać podstawiony dowolny string. W kodzie napis z parametrem możemy złożyć w następujący sposób:

String msg = getResources().getString(R.string.permission_denied_msg, "RECORD_AUDIO");

W Androidzie może podobać się sprawna podmiana napisów (i nazwy aplikacji) zaraz po zmianie języka w systemie, bez jego restartu.

Inną, nawet bardziej niezbędną sprawą jest podmiana domyślnej ikony aplikacji na swoją. Z obowiązującymi wytycznymi możemy zapoznać się na stronie https://www.google.com/design/spec/style/icons.html. Trafił mi się też post http://android-developers.blogspot.com/2013/07/making-beautiful-android-app-icons.html. Aby jednak dość szybko to zrobić skorzystałem ze stronki Android Asset Studio - Icon Generator - Launcher icons, która pozwala wygenerować zestaw ikon zgodny z obowiązującymi standardami.

To tyle na dziś.

P.S Załączam krótkie nagranie z działania app-ki, którego ostatnio zapomniałem udostępnić

sobota, 16 kwietnia 2016

BUILD 2016 - odc.3 (UAP, XAML, Xamarin)

Mały powrót do BUILD-a, 4 kolejne sesje związane z Windows i .NET.

Jeśli chodzi o uniwersalną platformę widać stopniowy jej rozwój i modyfikację. Nie ma tak spektakularnych zmian jak kiedyś, ale znajdziemy trochę pożytecznych nowości, a niektóre z nich sprawią nam wręcz radość jako twórcom. Ciekawie zapowiada się synchronizacja między urządzeniami niezależnie od platformy i wsparcie dla budowania rozszerzeń w oparciu o kontrakty (Edge tego używa).

Miłe drobne zmiany w XAML, jak wsparcie dla animowanych GIF-ów, łatwiejsza podmiana kolorystyki w kontrolkach danego typu, udoskonalenie CommandBar (m.in automatyczny overflow czy podawanie miejsca na labelki przycisków), zmiany z myślą o XBOX (m.in focusy, dźwięki), w pełni deklaratywne kontektowe menu, skróty klawiszowe, data binding wywołań metod i z rzutowaniem (nie potrzebujemy już nawet konwerter-ów), kontrolka TreeView (github, jak w Edge), nawigacja klawiaturą po Comboboxie, mocne wsparcie dla Ink, udoskonalenia w MediaPlayer, ItemsStackPanel. Mamy też efekty wizualne np. blur (co prawda trzeba napisać sporo dość kodu, nie to co kiedyś w Silverlight) czy niejawne animacje (demo z prostym kodem jak fajnie animować każdy element w gridzie jak zmieniamy rozmiar okna).

Prezentacja o Xamarin, hm… cóż jakoś mało odkrywcza, spodziewałem się czegoś znacznie więcej.

Cykl życia aplikacji - widać, że znów luzowane są reguły, że wolno coraz więcej. Podobać się może wykonywanie kodu w tle bez potrzeby tworzenia oddzielnego projektu z taskiem, wykonywanie w tle jak odtwarzanie audio bez linijki specyficznego kodu, tylko z deklaracją w manifeście (w Android jednak tak łatwo nie ma), możliwość przedłużenia wykonywania aplikacji lub jej usypiania o dowolną ilość czasu (jeśli stan energii lub user zezwoli na taką konfigurację). Niezabijanie od razu app-ki przy przekroczeniu zasobów to także objaw większego liberalizmu dla twórców.

 

Universal App Model Overview: What’s New in the UWP App Model

image

image

image

image

image

image

image

image

image

image

 

What's New in Windows UI/UX for Universal Windows Platform (UWP) Apps

image

image

image

image

image

CommandBar:  pozycjonowanie labelek do przycisków, auto overflow!

image

image

image

image

image

image

image

image

image

image

image

image

image

image

Wywoływanie metod w data bindingu

image

image

image

nie trzeba już konwerterów

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

 

Cross-Platform Mobile with Xamarin

image

image

image

image

 

Universal Windows Application Lifecycle: From Activation and Suspension to Background Execution, Multitasking and Extended Execution

image

image

image

image

image

image

image

image

image