Sean's Note: 2016

2016年12月4日 星期日

要怎麼查看 iOS Simulator 上 App 的 NSUserDefault?

~Library/Developer/CoreSimulator/Devices/{Simulator Id}/data/Containers/Data/Application/{Application Id}/Library/Preferences/{App name}.plist

2016年11月13日 星期日

Share plain text to other app

String shareBody = "Here is the share content body";
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject Here");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
startActivity(Intent.createChooser(sharingIntent, "Share Prompt Title"));
* Share Dialog

2016年10月17日 星期一

Android ReactiveX 新手入門

Getting Started With ReactiveX on Android


前言

在開發 Android 時,常常為了處理網路連線、後端 API 呼叫與 UI 的更新,而寫出一堆 callback,甚至是 nested callbacks,這些程式碼不僅難看,也容易寫錯。使用 ReativeX 可以讓程式碼更清楚明瞭,更可以節省開發的時間。

設定 RxAndroid

compile 'io.reactivex:rxandroid:1.2.1'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
compile 'io.reactivex:rxjava:1.2.1'
PS: 可以到 GitHub 上查看目前最新的版本

基礎:Observables 與 Observers 

當在開發 ReactiveX 時,我們會不斷的使用 observables 和 observers,因為 ReativeX 正正是套用 Observer Design Pattern 的設計,Observable 是可被觀察的對象,也就是真正在做事的人,Observer 是觀察者,聆聽與接收事件發生的人。

建立一個簡單只傳遞出 "Hello" 的 Observable:
Observable<string> myObservable = Observable.just("Hello"); // Emits "Hello"

建立一個 Observer:
Observer<string> myObserver = new Observer<string>() {
    @Override
    public void onCompleted() {
        // Called when the observable has no more data to emit
    }
 
    @Override
    public void onError(Throwable e) {
        // Called when the observable encounters an error
    }
 
    @Override
    public void onNext(String s) {
        // Called each time the observable emits data
        Log.d("OBSERVER1", s);
    }
};
在沒有 Observer 觀察者的時候,Observable 是不會傳遞出資料的,想要 Observable 傳遞出資料,就必須要先向其訂閱 (Subscribe):
Subscription mySubscription = myObservable.subscribe(myObserver);
此時 myObserver 就會接收到 "Hello" 字串。
除了 observer 可以 subscribe 以外,有時我們不需要 onCompletedonError 時,也可以用 Action1 的介面來 subscribe:
Subscription mySubscription = myObservable
.subscribe(new Action1<string>() {
    
        @Override

        public void call(String s) {
        
            Log.d("ACTION1", s);
    
        }

});

使用 Operators

from

ReactiveX 提供了許多對 observables 操作的運算方法,先來介紹 Observable.from 的用法,from 可以讓你提供一組數據資料,使每個資料都成爲一個 Observable,而 Observer 接受到的資料就會是一個接著一個來,而不是一次收到整組數據。
Observable<Integer> myArrayObservable 
    = Observable.from(new Integer[]{1, 2, 3, 4, 5, 6}); // Emits each item of the array, one at a time
 
myArrayObservable.subscribe(new Action1<Integer>() {
    @Override
    public void call(Integer i) {
        Log.d("My Action", String.valueOf(i)); // Prints the number received
    }
});

map

透過 map 可以將原本 Observable 的結果,做想要的運算,再回傳另一個新的 Observable。
        myArrayObservable
                .map(new Func1<Integer, Integer>() {
                    @Override
                    public Integer call(Integer integer) {
                        return integer * integer;
                    }
                })
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer i) {
                        Log.d("ACTION1", i.toString());
                }
        });

skip 

skip 可以用來忽略前面幾個的回傳結果,例如 skip(2) 回傳的結果就會是 3, 4, 5, 6
        myArrayObservable
                .skip(2)
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer i) {
                        Log.d("ACTION1", i.toString());
                }
        });

filter 

filter 如同其名,可以用來過濾掉不要的 observables,例如下方的範例就會過濾的奇數的值。
        myArrayObservable
                .filter(new Func1<Integer, Boolean>() {
                    @Override
                    public Boolean call(Integer integer) {
                        if (integer % 2 == 0) {
                            return true;
                        }
                        return false;
                    }
                })
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer i) {
                        Log.d("ACTION1", i.toString());
                    }
                });

處理 Asynchronous 任務

剛剛的任務都是執行在 UI Thread(Main Thread) 上的,如果我們要指定 observable 要在哪個 Thread 上執行任務,就要用 subscribeOn,subscribeOn(Schedulers.newThread) 或 subscribeOn(AndroidSchedulers.mainThread)。如果是要指定 observer 在那個 Thread 上接受資料,就要用 observeOn,如果沒有指名 observeOn,observer 就會在 subscribeOn 所指定的 thread 上接收到資料。
只是要傳遞簡單的資料,我們可以用前面介紹的 just 和 from 方法,但大多情況我們都需要執行其他的方法後,才能取得資料,此時就可以用 create 來建立 Observable,並實作 OnSubscribe 介面。
Observable<String> fetchFromGoogle = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        try {
            String data = fetchData("http://www.google.com");
            subscriber.onNext(data); // Emit the contents of the URL
            subscriber.onCompleted(); // Nothing more to emit
        }catch(Exception e){
            subscriber.onError(e); // In case there are network errors
        }
    }
});

zip

有時在處理網路任務時,會需要等待兩個以上的任務同時完成,並處理結果,這不但要添加不少程式碼,也容易寫錯,此時 ReactiveX 所提供的 zip 方法便帶來了這樣的好處:
// Fetch from both simultaneously
Observable<String> zipped 
    = Observable.zip(fetchFromGoogle, fetchFromYahoo, new Func2<String, String, String>() {
        @Override
        public String call(String google, String yahoo) { 
            // Do something with the results of both threads
            return google + "\n" + yahoo;
        }
    });

concat

concat 的功能和 from 很像,會把 observable 的結果一個接著一個回傳。
        
Observable observable1 = Observable.just("Hello");
Observable observable2 = Observable.just("World");

// Fetch from both simultaneously
Observable.concat(observable1, observable2)
          .subscribe(new Subscriber<string>() {
              @Override
              public void onCompleted() {}

              @Override
              public void onError(Throwable e) {}

              @Override
              public void onNext(String s) {
                  Log.d("Sean", s);
              }
});

Ref: https://code.tutsplus.com/tutorials/getting-started-with-reactivex-on-android--cms-24387

2016年10月14日 星期五

[Git] 如何更改 push 後的 commit 內容?

How to change git commit message after push?


若只是要更改近幾次的 commit message 可以用以下指令:
git commit --amend
然後在 force push 到 remote server:
git push --force <repository> <branch>
如果同時也有人在修改該 branch 時,最好是用 --force-with-lease 比較安全,因為如果 upstream 已經有更改時,push 會被 abort:
git push --force-with-lease <repository> <branch>
Ref: http://stackoverflow.com/questions/8981194/changing-git-commit-message-after-push-given-that-no-one-pulled-from-remote

2016年10月13日 星期四

[Android 4.4 Issue] TextView 無法垂直置中

The TextView cannot center vertically:

<TextView 

                android:id="@+id/toolbar_title" 
                android:layout_width="match_parent" 
                android:layout_height="match_parent"
                android:visibility="visible" 
                tools:text="Hello" 
                android:layout_gravity="center_vertical" 
                android:gravity="center_vertical" 
                android:textSize="@dimen/font_slarge"/> 

It seems an OS issue that android:gravity="center_vertical" doesn't work when android:layout_height="match_parent". The issue can be fixed when giving the specific height.


Ref: https://code.google.com/p/android/issues/detail?id=59368

2016年10月3日 星期一

[Mac] 如何在 ZSH 下設定 adb 指令?

How to add ADB command to ZSH?


在 ~/.zshrc 檔裡加入:
export PATH=$PATH:"$HOME/Library/Android/sdk/platform-tools"

2016年9月8日 星期四

[Mac] Leave the old terminal behind and try iTerm2+Oh My Zsh+Solarized color scheme

好用的 terminal tool,而且有較好的介面配色。

iTerm2 + Oh My Zsh + Solarized color scheme + Meslo powerline font (OS X / macOS)

https://gist.github.com/kevin-smets/8568070

2016年6月24日 星期五

Touch Event 的機制

Android 在處理 touch event 的傳遞可以分 ViewGroup 跟 View 來看,
兩者皆都透過 dispatchTouchEvent() 來傳遞事件,而 ViewGroup 會先交由 onInterceptTouchEvent() 來做處理(line 1960),預設是 return false 不做處理(可以透過繼承來改寫),找到第一個可視的 child 後,再呼叫 dispatchTransformedTouchEvent() (line 2049)。

 // ViewGroup.java Click here for full source.
1928  @Override
1929  public boolean dispatchTouchEvent(MotionEvent ev) {
1930      if ( != null) {
1931          .onTouchEvent(ev, 1);
1932      }
1934      // If the event targets the accessibility focused view and this is it, start
1935      // normal event dispatch. Maybe a descendant is what will handle the click.
1936      if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
1937          ev.setTargetAccessibilityFocus(false);
1938      }
1940      boolean handled = false;
1941      if (onFilterTouchEventForSecurity(ev)) {
1942          final int action = ev.getAction();
1943          final int actionMasked = action & .;
1945          // Handle an initial down.
1946          if (actionMasked == .) {
1947              // Throw away all previous state when starting a new touch gesture.
1948              // The framework may have dropped the up or cancel event for the previous gesture
1949              // due to an app switch, ANR, or some other state change.
1950              cancelAndClearTouchTargets(ev);
1951              resetTouchState();
1952          }
1954          // Check for interception.
1955          final boolean intercepted;
1956          if (actionMasked == .
1957                  ||  != null) {
1958              final boolean disallowIntercept = ( & ) != 0;
1959              if (!disallowIntercept) {
1960                  intercepted = onInterceptTouchEvent(ev);
1961                  ev.setAction(action); // restore action in case it was changed
1962              } else {
1963                  intercepted = false;
1964              }
1965          } else {
1966              // There are no touch targets and this action is not an initial down
1967              // so this view group continues to intercept touches.
1968              intercepted = true;
1969          }
1971          // If intercepted, start normal event dispatch. Also if there is already
1972          // a view that is handling the gesture, do normal event dispatch.
1973          if (intercepted ||  != null) {
1974              ev.setTargetAccessibilityFocus(false);
1975          }
1977          // Check for cancelation.
1978          final boolean canceled = resetCancelNextUpFlag(this)
1979                  || actionMasked == .;
1981          // Update list of touch targets for pointer down, if needed.
1982          final boolean split = ( & ) != 0;
1983          TouchTarget newTouchTarget = null;
1984          boolean alreadyDispatchedToNewTouchTarget = false;
1985          if (!canceled && !intercepted) {
1987              // If the event is targeting accessiiblity focus we give it to the
1988              // view that has accessibility focus and if it does not handle it
1989              // we clear the flag and dispatch the event to all children as usual.
1990              // We are looking up the accessibility focused host to avoid keeping
1991              // state since these events are very rare.
1992              View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
1993                      ? findChildWithAccessibilityFocus() : null;
1995              if (actionMasked == .
1996                      || (split && actionMasked == .)
1997                      || actionMasked == .) {
1998                  final int actionIndex = ev.getActionIndex(); // always 0 for down
1999                  final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
2000                          : .;
2002                  // Clean up earlier touch targets for this pointer id in case they
2003                  // have become out of sync.
2004                  removePointersFromTouchTargets(idBitsToAssign);
2006                  final int childrenCount = ;
2007                  if (newTouchTarget == null && childrenCount != 0) {
2008                      final float x = ev.getX(actionIndex);
2009                      final float y = ev.getY(actionIndex);
2010                      // Find a child that can receive the event.
2011                      // Scan children from front to back.
2012                      final ArrayList<View> preorderedList = buildOrderedChildList();
2013                      final boolean customOrder = preorderedList == null
2014                              && isChildrenDrawingOrderEnabled();
2015                      final View[] children = ;

如果沒有自定義 children 的 draw 順序的話,default 就是越晚加入 ViewGroup 的 child 也晚畫,所以從最上層的 child 來開始接收事件,最上層不收就在往下一層丟,直到 child 處理事件就跳開迴圈(line 2049)。

2016                      for (int i = childrenCount - 1; i >= 0; i--) {
2017                          final int childIndex = customOrder
2018                                  ? getChildDrawingOrder(childrenCount, i) : i;
2019                          final View child = (preorderedList == null)
2020                                  ? children[childIndex] : preorderedList.get(childIndex);
2022                          // If there is a view that has accessibility focus we want it
2023                          // to get the event first and if not handled we will perform a
2024                          // normal dispatch. We may do a double iteration but this is
2025                          // safer given the timeframe.
2026                          if (childWithAccessibilityFocus != null) {
2027                              if (childWithAccessibilityFocus != child) {
2028                                  continue;
2029                              }
2030                              childWithAccessibilityFocus = null;
2031                              i = childrenCount - 1;
2032                          }
2034                          if (!canViewReceivePointerEvents(child)
2035                                  || !isTransformedTouchPointInView(x, y, child, null)) {
2036                              ev.setTargetAccessibilityFocus(false);
2037                              continue;
2038                          }
2040                          newTouchTarget = getTouchTarget(child);
2041                          if (newTouchTarget != null) {
2042                              // Child is already receiving touch within its bounds.
2043                              // Give it the new pointer in addition to the ones it is handling.
2044                              newTouchTarget.pointerIdBits |= idBitsToAssign;
2045                              break;
2046                          }
2048                          resetCancelNextUpFlag(child);
2049                          if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
2050                              // Child wants to receive touch within its bounds.
2051                               = ev.getDownTime();
2052                              if (preorderedList != null) {
2053                                  // childIndex points into presorted list, find original index
2054                                  for (int j = 0; j < childrenCount; j++) {
2055                                      if (children[childIndex] == [j]) {
2056                                           = j;
2057                                          break;
2058                                      }
2059                                  }
2060                              } else {
2061                                   = childIndex;
2062                              }
2063                               = ev.getX();
2064                               = ev.getY();
2065                              newTouchTarget = addTouchTarget(child, idBitsToAssign);
2066                              alreadyDispatchedToNewTouchTarget = true;
2067                              break;
2068                          }
2070                          // The accessibility focus didn't handle the event, so clear
2071                          // the flag and do a normal dispatch to all children.
2072                          ev.setTargetAccessibilityFocus(false);
2073                      }
2074                      if (preorderedList != null) preorderedList.clear();
2075                  }
2077                  if (newTouchTarget == null &&  != null) {
2078                      // Did not find a child to receive the event.
2079                      // Assign the pointer to the least recently added target.
2080                      newTouchTarget = ;
2081                      while (newTouchTarget.next != null) {
2082                          newTouchTarget = newTouchTarget.next;
2083                      }
2084                      newTouchTarget.pointerIdBits |= idBitsToAssign;
2085                  }
2086              }
2087          }
2089         ...

可以看到 dispatchTransformedTouchEvent() 裡又會去呼叫 child 的 dispatchTouchEvent() (line 2405)。

 // ViewGroup.java Click here for full source.
2363  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
2364          View child, int desiredPointerIdBits) {
2365      final boolean handled;
2367      // Canceling motions is a special case.  We don't need to perform any transformations
2368      // or filtering.  The important part is the action, not the contents.
2369      final int oldAction = event.getAction();
2370      if (cancel || oldAction == .) {
2371          event.setAction(.);
2372          if (child == null) {
2373              handled = super.dispatchTouchEvent(event);
2374          } else {
2375              handled = child.dispatchTouchEvent(event);
2376          }
2377          event.setAction(oldAction);
2378          return handled;
2379      }
2381      // Calculate the number of pointers to deliver.
2382      final int oldPointerIdBits = event.getPointerIdBits();
2383      final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
2385      // If for some reason we ended up in an inconsistent state where it looks like we
2386      // might produce a motion event with no pointers in it, then drop the event.
2387      if (newPointerIdBits == 0) {
2388          return false;
2389      }
2391      // If the number of pointers is the same and we don't need to perform any fancy
2392      // irreversible transformations, then we can reuse the motion event for this
2393      // dispatch as long as we are careful to revert any changes we make.
2394      // Otherwise we need to make a copy.
2395      final MotionEvent transformedEvent;
2396      if (newPointerIdBits == oldPointerIdBits) {
2397          if (child == null || child.hasIdentityMatrix()) {
2398              if (child == null) {
2399                  handled = super.dispatchTouchEvent(event);
2400              } else {
2401                  final float offsetX =  - child.mLeft;
2402                  final float offsetY =  - child.mTop;
2403                  event.offsetLocation(offsetX, offsetY);
2405                  handled = child.dispatchTouchEvent(event);
2407                  event.offsetLocation(-offsetX, -offsetY);
2408              }
2409              return handled;
2410          }
2411          transformedEvent = MotionEvent.obtain(event);
2412      } else {
2413          transformedEvent = event.split(newPointerIdBits);
2414      }
2416      // Perform any necessary transformations and dispatch.
2417      if (child == null) {
2418          handled = super.dispatchTouchEvent(transformedEvent);
2419      } else {
2420          final float offsetX =  - child.mLeft;
2421          final float offsetY =  - child.mTop;
2422          transformedEvent.offsetLocation(offsetX, offsetY);
2423          if (! child.hasIdentityMatrix()) {
2424              transformedEvent.transform(child.getInverseMatrix());
2425          }
2427          handled = child.dispatchTouchEvent(transformedEvent);
2428      }
2430      // Done.
2431      transformedEvent.recycle();
2432      return handled;
2433  }

接著跳轉到 View 的 dispatchTouchEvent() 來看,可以發現View 會先處理 user 定義的 onTouch() 事件(line 8467),如果 user 不處理(返回 false),系統才會呼叫 onTouchEvent() (line 8471) 來進一步觸發常用的 onClick()或 onLongClick() callback

// View.java  Click here for full source.
8439  public boolean dispatchTouchEvent(MotionEvent event) {
8440      // If the event should be handled by accessibility focus first.
8441      if (event.isTargetAccessibilityFocus()) {
8442          // We don't have focus or no virtual descendant has it, do not handle the event.
8443          if (!isAccessibilityFocusedViewOrHost()) {
8444              return false;
8445          }
8446          // We have focus and got the event, then use normal event dispatch.
8447          event.setTargetAccessibilityFocus(false);
8448      }
8450      boolean result = false;
8452      if ( != null) {
8453          .onTouchEvent(event, 0);
8454      }
8456      final int actionMasked = event.getActionMasked();
8457      if (actionMasked == .) {
8458          // Defensive cleanup for new gesture
8459          stopNestedScroll();
8460      }
8462      if (onFilterTouchEventForSecurity(event)) {
8463          //noinspection SimplifiableIfStatement
8464          ListenerInfo li = ;
8465          if (li != null && li.mOnTouchListener != null
8466                  && ( & ) == 
8467                  && li.mOnTouchListener.onTouch(this, event)) {
8468              result = true;
8469          }
8471          if (!result && onTouchEvent(event)) {
8472              result = true;
8473          }
8474      }
8476      if (!result &&  != null) {
8477          .onUnhandledEvent(event, 0);
8478      }
8480      // Clean up after nested scrolls if this is the end of a gesture;
8481      // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
8482      // of the gesture.
8483      if (actionMasked == . ||
8484              actionMasked == . ||
8485              (actionMasked == . && !result)) {
8486          stopNestedScroll();
8487      }
8489      return result;
8490  }