2014年7月23日 星期三

Android 開發 (六十) 開發第一支自己的app

小弟最近下班花了點時間,開發了一支Rss Reader,
甚麼是Rss Reader呢?  其實就是去截取各個網站的Rss 文章,並且截取到我的app中

(一)要如何取得Rss的資料呢?

其實有很多resource, 像是google api 、 feedly、newsBlur ,
不過小弟覺得feedly比較符合需求,所以就使用了feedly當作主要的資料來源,
feedly的api其實很容易使用,下面的網站有相關的資料,http://developer.feedly.com/


(二) 快速的開發

由於小弟開發的時間只有下班時間,而且並不是每天都能夠順利的進行開發,在時間很零碎而且不足夠的情況下,要如何提升開發效率就是另一個問題了,為了要達到快速開發的目標,就必須減少建構基礎架構的時間,所以小弟使用了許多github上的resource來加速開發,

https://github.com/astuetz/PagerSlidingTabStrip
https://github.com/etsy/AndroidStaggeredGrid
Gson
jsoup
https://github.com/nostra13/Android-Universal-Image-Loader
https://github.com/square/retrofit
https://github.com/greenrobot/EventBus
https://github.com/bauerca/drag-sort-listview
https://github.com/dmytrodanylyk/circular-progress-button

首先利用PagerSlidingTabStrip、circular-progress-button 來完成較好的UserExperience ,



AndroidStaggeredGrid 來完成較特殊的layout排法,


Gson 、jsoup、 ImageLoader 幫助我資料的轉換以及取得網頁的資料還有圖片的讀取
retrofit負責 http get and post
EventBus負責元件間的資料傳遞
drag-sort-listview讓我能夠快速的完成重新排序以及刪除訂閱資料的功能


講了那麼多,最後當然要附上下載連結給大家參考一下 XD

https://play.google.com/store/apps/details?id=com.yesnews.sample


說了那麼多,挑戰其實是在app release之後啊XD

Android 開發 (五十九) Eclipse with ProGuard

在 crashlytics 介紹中有提到如何使用crashlytics,但是在經過proguard之後,所有的crash

log都被混淆無法判斷錯誤的來源,要如何解決這個問題呢?

crashlytics的網站中有提到相關的解法


如下

Eclipse with ProGuard

Crashlytics automatically de-obfuscates stack traces in your crash reports. You’ll never manually retrace a stack trace or hunt around for a lost mapping file again!
For Crashlytics to provide the most informative stack traces, add the following line to your ProGuard configuration file:
-keepattributes SourceFile,LineNumberTable
When building for release with Eclipse, export your application to an APK using the “Export Crashlytics-enabled Android Application” exporter from the Eclipse export menu. Just go to File -> Export -> Android :)
This exports your application using the standard ADT Android Application exporter and then uploads the ProGuard-generated mappings file to our servers.
If you make release builds using the command line, please see the appropriate article for your build system:

2014年7月20日 星期日

Android 開發 (五十八) easy way to get facebook Response data

使用facebook的api 例如 使用facebook SDK po 塗鴉牆 以及tag好友
裡面所使用的post wall 裡的call back

public void onCompleted(Response response) {
      // do something
}

通常我們取得資料後會將回傳資料轉成string然後再依照 gson的方式去取得資料,
不過當我們使用facebook的response時,我們會發現他有
public final GraphObject getGraphObjectAs(Class graphObjectClass)的method,然而在facebook的文件裡面沒有提到的是,我們可以利用這個api來取得想要的資料,舉例來說,假設我們知道回傳的資料裡有id,那我們可以定義一個interface如下

interface MyDataGetter{
   int getId();
}


MyDataGetterresult = response.getGraphObjectAs(MyDataGetter.class);

我們可以輕鬆的取得我們需要的資料。

2014年7月9日 星期三

Android 開發 (五十六) Gradle 如何增加lib

Gradle 跟eclipse的行為很不一樣,在eclipse中當我們缺少lib,需要將lib檔加入 libs folder或者加入 android lib中


但是在 android studio中一切都變了,如今...要import library變得更簡單,我們不再需要繁瑣的import 沒用的project,也不需要每次為了幫別人架設環境用半天了

在 Android Studio 中我們可以利用 file -> Project Structure -> Dependencies 增加我們需要的lib


假設我們需要import external的lib,變得更簡單了,以前我們可能必須import一大堆的project到我們的專案中,現在這一切已經不再必要,你只需要知道版本號之後只需要在上面的search欄中填入你的lib 並且點選ok就完成了,舉個簡單的例子,例如我的project想要import eventbus這個功能
我只需要將 
dependencies {
    compile 'de.greenrobot:eventbus:2.2.1'
}
加入我的dependencies中即可,剩下的gradle會幫你解決所有的問題。

真的是超級方便啊,不過最近在0.81 版本上遇到一個問題,

Manifest merger failed : uses-sdk:minSdkVersion xx cannot be smaller than version L declared in library com.android.support:support-v4:21.0.0-rc1

感覺是0.8的bug,看起來是因為gradle設定檔內使用
compile 'com.android.support:support-v4:+' 造成 gradle去使用最新版本的 support-v4 然而最新版本的 support-v4似乎只支援 L, 所以必須將上面的 dependencies 改成  compile 'com.android.support:support-v4:20.0.0' 這樣就解決了。

Android studio真的很方便,而且也進入beta了,如果是寫新app的人可以直接切過來了,至於必須維護舊app的人,這條路也是必經之路,現在這個時間點也是可以考慮切換的時候了,畢竟eclipse真的很麻煩啊XD。

Android 開發 (五十五) GoogleCloudMessaging

最近在玩Gradle的時候意外發現

GCMRegistrar  Is  Deprecated  



我記得在我剛接觸android時...GCM才剛改版過...如今過了不到一年...他又改版了...

雖然說又改版了,不過使用方式並沒有太大的改變。只是新版的register不能在MAIN_THREAD中執行,必須使用async task去做...

附上簡單的執行片段


        new AsyncTask<Void, Void, String>() {
            @Override
            protected String doInBackground(Void... params) {
                String msg;
                try{
                    GoogleCloudMessaging gcm;

                    gcm = GoogleCloudMessaging.getInstance(getApplicationContext());

                    String regid = gcm.register(SENDER_ID);
                    Log.d("Ted","id "+regid);
                    msg = "Device registered, registration ID=" + regid;
                } catch (IOException ex) {
                    msg = "Error :" + ex.getMessage();
                    // If there is an error, don't just keep trying to register.
                    // Require the user to click a button again, or perform
                    // exponential back-off.
                }
                return msg;
            }

            @Override
            protected void onPostExecute(String msg) {

            }
        }.execute(null, null, null);

regid 就是server訊息傳送時所需的token,根據sample code的寫法,只有在第一次執行的時候才會執行register 其他時候都是由sharepreference取得之前的regid (這代表著regid不變?這點我很懷疑)。

其他的使用方式跟以前沒太大的差別,有興趣可以參考 Hello GCM
最後附上 sample code

Android 開發 (五十四) 客製化 progress bar

通常內建的progressbar都長的不是非常的好看,假設我們今天想要客製化一個自己的progress bar,



我們可以利用drawable + Animatable 來完成我們想要的客製化progress bar , 主要轉動的部分當然是由animation來完成的,我們只需要利用drawArc,並且使用animation來更改餵進去的參數即可,
    @Override
    public void draw(Canvas canvas) {
        canvas.drawArc(mBounds, mCurrentAngle, 340, false, mPaint);
    }
不斷的call draw並且更改mCurrentAngle造成progressbar轉動的效果

至於改變參數的方法可以參考下面的code

        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
        mObjectAnimatorAngle.setInterpolator(new LinearInterpolator());
        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);

這是4.0之後的寫法,在property改變的時候去設定值,並且call invalidateSelf()使得draw被重複trigger


    private Property<MyProgressDrawable, Float> mAngleProperty
            = new Property<MyProgressDrawable, Float>(Float.class, "angle") {
        @Override
        public Float get(MyProgressDrawable object) {
            return object.getCurrentAngle();
        }

        @Override
        public void set(MyProgressDrawable object, Float value) {
            object.setCurrentAngle(value);
        }
    };
    public void setCurrentAngle(Float angle) {
        mCurrentAngle = angle;
        invalidateSelf();
    }

最後附上 sample code

2014年7月2日 星期三

Android 開發 (五十三) material Design 簡介

Android 開發 (五十二) facebook login + Nest Fragment

當使用facebook login,出現偶發無法login 並且查詢status為 opening的時候,就必須注意是否已經踏入了facebook api的坑中。

facebook login api 必須在每個state 中做事情例如

    public void onStart() {       
        Session.getActiveSession().addCallback(this);
    }

    public void onStop() {
        Session.getActiveSession().removeCallback(this);
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
     Session.getActiveSession().onActivityResult((Activity)context, requestCode, resultCode, data);
    }

當我們在Nest Fragment的時候(ex :TabHost+ Viewpager),onActivityResult可能會沒有被呼叫到,
這就會造成login失敗,最好的方法就是不要在Nest Fragment裡做 facebook login,否則就必須想辦法在ParentFragment (通常為TabHost的那個Fragment)中呼叫 子fragment 的onActivityResult
不過這個方法也不能100%的避掉問題,有時候 ParentFragment的 onActivityResult也不會被呼叫到。

所以回到最原始的解法,當必須在Nest Fragment中呼叫login時,還是偷偷開一個fragment或Activity做login的動作吧..

Android 開發 (五十一) facebook SDK 傳送 xmpp訊息 v1.0 facebook before 3.14

要使用facebook 傳送訊息是一個災難,因為能參考的資料非常少,facebook也只說明要使用xmpp來傳送訊息,所以在這邊提供一些Sample給大家參考。

首先先提供 相關檔案
https://drive.google.com/file/d/0B7nu3f_AuGWEM3l3WGJBQXp3R0E/edit?usp=sharing

裡面有三個檔案

這兩個檔案是跟xmpp相關的設定,不需要做修改,直接丟進workspace裡就行了
FacebookChatManager.java
SASLXFacebookPlatformMecha.java

第三個檔案
SendMessageIntentService.java

我這邊是使用background service來傳送 message 如果有仔細去看code會發現在傳送訊息之間我有使用sleep,這是因為我發現不間斷的傳送會讓訊息loss掉,所以使用這個方式(算是暫時可用的解法)

要使用這個功能,首先必須login 並且擁有"xmpp_login"的權限 如果已經login的話可以使用
newPermission來增加擁有的權限

  Session.NewPermissionsRequest newPermissionsRequest = new Session.NewPermissionsRequest(
    (Activity)context, newPermission);
  newPermissionsRequest.setCallback(statusCallback);
  Session.getActiveSession().requestNewPublishPermissions(newPermissionsRequest);

只需要在newPermission裡面塞入 "xmpp_login"即可

接著稍微看一下 service裡面做的事情


 @Override
 protected void onHandleIntent(Intent intent) {
  // TODO Auto-generated method stub
  nameList = getNameList(intent);
  if(nameList.size()>0){
   message = intent.getStringExtra(MSG_EXTRA);
   FacebookChatManager facebookChatManager = FacebookChatManager.getInstance(this);

   if (facebookChatManager.connect()) {    
    if (facebookChatManager.logIn(getString(R.string.fb_app_id), Session
      .getActiveSession().getAccessToken())) {

     List<String> resultList = facebookChatManager.findSendMessageList(nameList);    
     facebookChatManager.sendBatchMessage(resultList, message);     
    }

    facebookChatManager.disConnect();

   }
  } 
 }

我將message 以及 friend Id 傳進來在connect 完成之後就使用sendbatch的方式將訊息傳遞出去

facebook目前已經有提供新的做法,但是該做法必須安裝facebook messager 詳細的內容可以參考 http://tedforum.blogspot.tw/2014/05/android-facebook-v20314sdk-chat-api.html

Android 開發 (五十) 使用facebook SDK po 塗鴉牆 以及tag好友

該如何使用facebook sdk post 自己的塗鴨牆呢?
我們可以利用Request.newPostRequest以下是範例

public class FacebookPostMyWall {
 public static class ObjectBuilder{
  private GraphObject statusUpdate ;
  public ObjectBuilder(){
   statusUpdate = GraphObject.Factory.create();
  }
  public ObjectBuilder setMessage(String msg){
   statusUpdate.setProperty("message", msg);
   return this;
  }
  public ObjectBuilder setLink(String link){
   statusUpdate.setProperty("link", link);
   return this;
  }
  public ObjectBuilder setPicture(String url){
   statusUpdate.setProperty("picture", url);
   return this;
  }
  public ObjectBuilder setName(String name){
   statusUpdate.setProperty("name", name);
   return this;
  }
  public ObjectBuilder setDescription(String des){
   statusUpdate.setProperty("description", des);
   return this;
  }
  public GraphObject build(){
   return statusUpdate;
  }
 }
 public void post(ObjectBuilder builder,Callback callback) {
  Request request = Request.newPostRequest(Session.getActiveSession(), "me/feed",
    builder.build(), callback);
  request.executeAsync();
 }
}
利用上面的方法,我可以直接將我們需要的資料直接填入我們的塗鴉牆,注意到這裡,facebook禁止我們在使用者輸入message的地方輸入字串,也就是message的地方不應該預填任何資料,這部分的資料必須由使用者填入,否則facebook的演算法會將我們的app ban掉。

上面的方法不需要跳出dialog就可以將訊息傳遞出去,是個非常方便的api

注意到上面的方法只能post在自己的塗鴉牆,如果我們希望post在好友的塗鴉牆,那該怎麼做呢? 上面的方法並無法達到我們希望的功能,所以必須利用別的方法來完成我們希望的功能,
可以利用下方的方式。


FeedDialogBuilder builder = new WebDialog.FeedDialogBuilder(getActivity());
builder.setName(name)
.setCaption(caption)
.setLink(link)    
.setTo(id)
.setDescription(Description)
.setPicture(FBshareHelper.createPicture());

builder.build().show();


這個方法其實是用來分享資料的預設是share到自己的塗鴉牆,但是當我們將setTo的Id帶成別人的塗鴉牆時,就會貼到朋友的塗鴉牆上,不過這個api有個非常大的壞處,當需要分享給多個好友時,就必須一個一個設,而且每次都會跳出dialog要使用者按下分享,應該說他本來的功能就是在分享文章的時候使用的api。

接著如果我們希望tag好友,我們可以使用下面的方法
首先我們必須先做setup
private UiLifecycleHelper uiHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    uiHelper = new UiLifecycleHelper(this, null);
    uiHelper.onCreate(savedInstanceState);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    uiHelper.onActivityResult(requestCode, resultCode, data, new FacebookDialog.Callback() {
        @Override
        public void onError(FacebookDialog.PendingCall pendingCall, Exception error, Bundle data) {
            Log.e("Activity", String.format("Error: %s", error.toString()));
        }

        @Override
        public void onComplete(FacebookDialog.PendingCall pendingCall, Bundle data) {
            Log.i("Activity", "Success!");
        }
    });
}
@Override
protected void onResume() {
    super.onResume();
    uiHelper.onResume();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    uiHelper.onSaveInstanceState(outState);
}

@Override
public void onPause() {
    super.onPause();
    uiHelper.onPause();
}

@Override
public void onDestroy() {
    super.onDestroy();
    uiHelper.onDestroy();
}
在設定時,很重要的一點要特別注意,如果你是在Nest Fragment(TabHost + viewPager) 裡面使用這種api 要非常注意,onActivityResult可能不會被呼叫到,這可能會造成功能異常。

在設定完成後接著只需要呼叫下面的code就行了
FacebookDialog shareDialog = new FacebookDialog.ShareDialogBuilder(this)
        .setLink("https://developers.facebook.com/android")
        .build();
uiHelper.trackPendingDialogCall(shareDialog.present());
不過這樣並沒有tag到我們的好友所以必須加上一行  setFriends(List)
例如 setFriends(Arrays.asList("id1","id2");
這樣就可以將好友tag在我們的文章中

這個api的缺點也是在傳送的時候會跳出dialog,無法使用背景傳送。
facebook sdk有很多東西可以學習,不過也有很多坑啊,小弟真的花了很多時間在跳坑XDD



Android 開發 (四十九) 取得好友名單 facebook v1 before 3.14

在3.14版本(5/1)號以前的facebook app可以取得好友名單,要怎麼取得好友名單呢,方法很簡單

只需要使用Request.newMyFriendsRequest API 就可以取得好友清單,
範例如下

 public void requestFriend(){
  
   Request request = Request.newMyFriendsRequest(Session.getActiveSession(),
              new Request.GraphUserListCallback() {

      @Override
      public void onCompleted(List<GraphUser> users, Response response) {
       if(users!=null){
        String uri = null;
        List<FriendModel> FriendData= new ArrayList<FacebookFriend.FriendModel>();
        Map<String, FriendModel> maps = new HashMap<String, FacebookFriend.FriendModel>();
        for(GraphUser user :users){
         FriendModel model = new FriendModel(user);
         FriendData.add(model); 
         maps.put(model.Id, model);
        }
                     

       }
      }
                  
              });
   Bundle params = new Bundle();
         params.putString("fields", "id,name,picture");
         request.setParameters(params);
         request.executeAsync();
 }

在params中加入的fields代表查詢時會回傳的資料,在取得GraphUser 之後,就是要想辦法取得userId,userPicture了,該怎麼做呢?  看看範例




 public static class FriendModel{
  private String name;
  private String Id;
  private String picture;
  public FriendModel(GraphUser user){
   name = user.getName();
   Id = user.getId();
   picture  = getPicture(user);
  }
  public FriendModel(String _name,String _Id,String _picture){
   name = _name;
   Id = _Id;
   picture  = _picture;     
  }
  
  public String getName(){
   return name;
  }
  
  public String getId(){
   return Id;
  }
  
  public String getPicture(){
   return picture;
  }
  
  private String getPicture(GraphUser user){
   String uri="";
   Object o = user.getProperty("picture");
         if (o instanceof String) {
             uri = (String) o;
         } else if (o instanceof JSONObject) {
             ItemPicture itemPicture = GraphObject.Factory.create((JSONObject) o).cast(ItemPicture.class);
             ItemPictureData data = itemPicture.getData();
             if (data != null) {
                 uri = data.getUrl();
             }
         }
         uri = uri.replace("s50x50", "s100x100");
         return uri;
  }
 }
 private interface ItemPicture extends GraphObject {
        ItemPictureData getData();
    }

    // Graph object type to navigate the JSON that sometimes comes back instead of a URL string
    private interface ItemPictureData extends GraphObject {
        String getUrl();
    }


要圖案的話就必須利用上面的方式取得user的圖案,但是這邊有個Tricky的點,就是我將取得的圖檔轉成了100x100 不過這種方法在某些情況下可能會取不到user的圖。 在取得好友圖像以及ID之後就可以做更多的事情啦,例如tag好友,POST朋友塗鴉牆,invite好友....etc