2015年2月26日木曜日

Swiftでパラメータ付きPOSTリクエストを行う

SwiftでWebAPIのリクエストを行う際、POSTメソッドを使う必要がありました。
次は非同期でリクエストを行う、NSURLConnectionクラスのsendAsynchronousRequestメソッドを用いたサンプルソースです。

        // URLセット
        let url = NSURL(string: "https://test.url.jp")
        var request : NSMutableURLRequest = NSMutableURLRequest(URL: url!)

        // POSTメソッド指定
        request.HTTPMethod = "POST"
        
        // POSTパラメータ
        var bodyData: String = "key1=value1&key2=value2"
        request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
        // ヘッダの指定
        request.setValue("HeaderValue", forHTTPHeaderField: "HeaderName")
        
        //asyncで実行
        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: responseHandler)

2015年2月24日火曜日

SwiftなCocoa Touch FrameworkでCommonCryptoを使う

Swiftを使ってCocoa Touch Frameworkを作成しています。
どうしても、CommonCryptoを使う必要があり手段を探していました。

以下、参考になったサイトです。
Importing CommonCrypto in a Swift framework
CommonHMAC in Swift

手順は次のようになります。

  1. CommonCryptoというディレクトリを作成
  2. module.mapというファイルを作成
  3. プロジェクト設定のBuild Settings -> Swift Compiler - Search Paths -> Import Pathsに、上記のCommonCryptoディレクトリを指定



次はサンプルです。 module.mapにSDK内のヘッダファイルのPathを記述します。

module CommonCrypto [system] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
    link "CommonCrypto"
    export *
}

このあたり、公式Developerサイトに書いてるのかな?

2015年2月21日土曜日

Unit testで非同期処理のWaitをする

XcodeでiOS開発の勉強をはじめました。
いきなりですが、UnitTestネタです。テストしないもの、エンジニアにあらずです。

参考:Writing Test Classes and Methods

waitForExpectationsWithTimeoutで非同期処理の待ち状態を指定

標準テストフレームワークのXCTestExpectationとXCTestCase.waitForExpectationsWithTimeoutを組み合わせて実装します。
手順は次のようになります。

  1. XCTestCase.expectationWithDescriptionをコールして、XCTestExpectationを取得 
  2. XCTestCase.waitForExpectationsWithTimeoutで、waitを指定 
  3. 非同期処理終了のタイミングで、XCTestExpectation.fulfill()。もし、waitForExpectationsWithTimeoutで指定した時間内にコールしなければfailとなる

サンプルプログラムです。
    func testPerformAsyncRequest(){
        // XCTestExpectationの取得
        let expectation = self.expectationWithDescription("client key")

        // 非同期処理のコールバック処理完了後、expectation.fulfill()をコール
        expectation.fulfill()
        
        //waitの時間指定
        self.waitForExpectationsWithTimeout(5, handler: nil)
        
    }

2015年2月8日日曜日

コードカバレッジを使って、視覚的/統計的にテスト状況を確認する

build.gradleの設定

Code Coverage機能を使用するために、app直下のbuild.gradleに追加します。

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }

+        debug {
+            testCoverageEnabled = true
+        }
    }
}

複数のProjectをImportしている場合、各モジュールのbuild.gradleにtestCoverageEnabledを追加します。

Code Coverageの実行

Terminalから次のコマンドを実行します。

./gradlew createDebugCoverageReport

createDebugCoverageReportを実行すると、自動的にandroidTestを行います。
デバイス(or エミュレータ)が起動していない場合、次のようなエラーが表示されます。

:app:connectedAndroidTest FAILED          
              
FAILURE: Build failed with an exception.
              
* What went wrong:
Execution failed for task ':app:connectedAndroidTest'.
> com.android.builder.testing.api.DeviceException: java.lang.RuntimeException: No connected devices!

必ずデバイスかエミュレータを起動して、androidTestを実行できる環境にしておきましょう。

レポートの観覧

createDebugCoverageReportが成功すると、各モジュールのOutputにレポートを保存します。
[PROJECT]/app/build/outputs/reports/coverage/debug/index.html

次のような表が出力されます。コードの全ライン数に対して、テストが実行されたラインが統計的に確認する事ができます。



次のように視覚的に、どのコードがテストされていないか確認することも可能です。

2015年2月1日日曜日

LocalBroadcastManagerを使って、アプリ内部にのみブロードキャストを行う

LocalBroadcastManager

LocalBroadcastManagerはアプリ内の同プロセスに限定して、Broadcastを行う事ができます。
通常のsendBroadcastでブロードキャストと異なり、次のような利点があります。

プライベートなデータをアプリ外に残す心配がない。
他アプリがこのBroadcastを使う事ができない。(セキュリティホールの心配はしなくていい)
通常のBroadcastに比べ、より効率的アプリ内部へブロードキャストが行える。

注意点としては「同プロセス」に限定してブロードキャストを行います。言い換えれば、他プロセスにはブロードキャストは行えません。


用途

データ管理を行うModleクラスを作成する際、多くの人はServiceで実装すると思います。
Serviceを使うまでもない軽微なModleなら、ObserverとしてLocalBroadcastManagerを利用してデータの更新通知を行うのもありかと思います。


サンプルソース

こちらにJUnit付きのソースをアップしています。
https://github.com/baroqueworksdev/MyApiDemo_AndroidStudio/commit/cf08208cfd84b916480a08acf6f43fd791ca7da0

public class LocalBroadcastController {
    private LocalBroadcastManager mLocalBroadcastManager;
    private IntentFilter mIntentFilter;
    private OnLocalBroadcastController mOnLocalBroadcastController;

    public interface OnLocalBroadcastController {
        public void onReceive(Context context, Intent intent);
    }

    /**
     * Constructor
     *
     * @param context  Context
     * @param filter   Intent Filter which need to receive an action
     * @param listener onReceive
     */
    public LocalBroadcastController(Context context, IntentFilter filter, OnLocalBroadcastController listener) {
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
        mIntentFilter = filter;
        mOnLocalBroadcastController = listener;
    }

    /**
     * Register Receiver
     */
    public void registerReceiver() {
        mLocalBroadcastManager.registerReceiver(mReceiver, mIntentFilter);
    }

    /**
     * Unregister Receiver
     */
    public void unregisterReceiver() {
        mLocalBroadcastManager.unregisterReceiver(mReceiver);
    }


    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mOnLocalBroadcastController.onReceive(context, intent);

        }
    };

}

2015年1月25日日曜日

Android StudioでZXingのライブラリを使う

Android StudioでZXingのライブラリを使う設定です。
非常に簡単で、アプリのbuild.gradleに追加するだけです。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.2'
+    compile 'com.google.zxing:core:3.1.0'
+    compile 'com.google.zxing:android-integration:3.1.0'

}

2015年1月17日土曜日

Volleyのリクエストにパラメータとヘッダーの値を追加する方法

Volleyのリクエストにパラメータとヘッダーの値を追加する方法です。
StringRequestクラスの親クラスであるRequestクラスに、次のようなメソッドがあります。

     /**
     * Returns a list of extra HTTP headers to go along with this request. Can
     * throw {@link AuthFailureError} as authentication may be required to
     * provide these values.
     * @throws AuthFailureError In the event of auth failure
     */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

     /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

StringRequestを生成する際、getHeaders()とgetParams()をオーバーライドすれば、POSTパラメータとヘッダーの値を追加することができます。
次のソースはオーバーライドによって値を渡すサンプルです。


    /**
     * Request(StringRequest) with params
     *
     * @param url      request url
     * @param listener listener for Response or Error
     * @param params   value of setting Http Params
     * @param headers  value of setting Http headers
     */
    public void get(String url, final ResponseListener listener, final Map<String, String> params
            , final Map<String, String> headers) {
 
        StringRequest request = new StringRequest(url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String s) {
                        listener.onResponse(s);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError volleyError) {
                        listener.onErrorResponse(volleyError);
                    }
                }
        ) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                return params;
            }
 
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                return headers;
            }
        };
 
        mRequestQueue.add(request);
    }