JQ Blog

ビルド環境設定(XCode)

XCode

XCodeの場合はTargetを分ける方法とConfigurationsを分ける方法の二つの方法がある。

◎ Configurationsを分ける方法

まず、プロジェクトのinfoタブでConfigurationsというところで追加させる。ReleaseとDebugがDefaultされてあるのでその中にStagingを入れてみる。
Alt text +を押し、DebugかReleaseをduplicateして名前を変える。 Alt text 続いてTargetsのBuildSettingsの+を押し、User-DefinedにBUNDLE_DISPLAY_NAME_PREFIXとBUNDLE_IDENTIFIER_SUFFIXを追加する。そしてActiveConpilationConditionsも下のように変える。BUNDLE_DISPLAY_NAME_PREFIXはアプリ名を分けるために追加、BUNDLE_IDENTIFIER_SUFFIXはアプリのBundleIdをそれぞれ分けるために追加する。また、ActiveConpilationConditionsはそれぞれのビルドの機能を分岐させるために追加する。 Alt text Alt text 次はschemeを分けて管理する。左上のschemeのところでNew Schemeを押してDebugとStagingのschemeを追加する。また、Edit SchemeでRelease,Debug,Stagingのそれぞれの設定をする。 Alt text Alt text そこまでできたらTargetsのinfoでBundle identifierとBundle nameを変更する。Bundle identifierは$(PRODUCT_BUNDLE_IDENTIFIER)の後ろに$(BUNDLE_IDENTIFIER_SUFFIX)を、Bundle nameは$(PRODUCT_NAME)の前に$(BUNDLE_DISPLAY_NAME_PREFIX)を入れる。 Alt text そうすれば下のようにGeneralで変更されたのを確認できる。 Alt text それぞれのビルドに機能を分岐させる場合は下のように#ifを使う。#ifのところにさっきのActiveConpilationConditionsに設定したのを入れる。 Alt text ビルド別にビルドしてみると、

Release

Alt text

Debug

Alt text

Staging

Alt text それぞれ違うソースコードが実行されたことをみることができる。
インストールされたデバイスをみたら、 Alt text 三つのアプリがインストールされる。

◎ Targetを分ける方法

今のTargetをduplicateしてもう一つのTargetを作る。もちろん+で追加しても良いが、同じソースコードを使うならばduplicateでも全然構わない。作ったら名前を変更し、また自動にinfo.plistファイルも生成されるのでそれのファイル名も変更。その後にGeneralでinfo.plistを選んで設定する。 Alt text そしてBuild SettingsでProduct Bundle Identifierを変更すればする。 Alt text さっきと同じようにschemeを追加する。 Alt text ActiveConpilationConditionsをカスタマイズするが、Targetを間違わないよう気をつける。 Alt text さっきの分岐のうえにさらに分岐してみる。 Alt text すると、新たのプロジェクトのRelease,Debug,Stagingをそれぞれ使うことができる。 Alt text Alt text Alt text そして、デバイスで確認してみるとまた三つが増えて六つもアプリがインストールされたのを確認できる。 Alt text

ビルド環境設定(Android Studio)

アプリを開発するとき、ビルドを分けて開発することになるので、ビルド環境を分ける方法についてまとめてみた。

AndroidStudio

AndroidStudioの場合はモジュールのbuild.gradleで設定ができる。
最初プロジェクトを作ってbuild.gradleをみると下のようにdebugとreleaseがdefaultに設定されているのが確認できる。 Alt text 環境を分けるには二つの方法がある。buildTypesを追加するのとproductFlavorsを追加すること。

BuildTypes

まず、staging環境を作りたいとしたら、
上段のバーの Build -> Edit Build Types のところでビルドタイプを下のように追加する。
Alt text Alt text すると、buildTypesに追加されることが確認できる。
Alt text もし、それぞれの機能や設定を指定する場合は、たとえば、WebviewのそれぞれのurlをセッティングするときだとしたらBuildConfig.BUILD_TYPEメソッドを使って分岐して処理することができる。

1
2
3
4
5
6
7
if (BuildConfig.BUILD_TYPE.equals("debug")) {
    mWebView.loadUrl(DEV_URL);
} else if (BuildConfig.BUILD_TYPE.equals("staging")) {
    mWebView.loadUrl(STG_URL);
} else {
    mWebView.loadUrl(RELEASE_URL);
}


ProductFlavors

ここで同じソースコードを使って別のアプリを作らなきゃいけないときがあるかもしれない。たとえば、無料バージョンと有料バージョンを分けるとしたらProductFlavorsを設定する。
上段のバーの Build -> Edit Flavor のところでflavorを追加する。 Alt text Alt text すると、productFlavorsに追加されることが確認できる。 Alt text productFlavorsもBuildConfig.FLAVORを使って分岐ができる。

1
2
3
4
5
if (BuildConfig.FLAVOR.equals("free")) {
    mWebView.loadUrl(FREE_URL);
} else {
    mWebView.loadUrl(PAID_URL);
}


上の画像をみたらわかるように各flavorの下にbuildTypesがつく。今までのセッティングでfreeDebug,freeStaging,freeRelease,paidDebug,paidStaging,paidRelease全部で6個のビルドが生成される。
で、問題はこの分けられた6個のビルドのapplicationIdやversionNameが同じでインストールやリリースのときにうまくできないことだ。なので、applicationIdSuffix,versionNameSuffixを使ってそれぞれのapplicationIdとversionNameを持つようにする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
buildTypes {
    release {
        ...

        ...
    }
    staging {
        ...

        applicationIdSuffix '.staging'
        versionNameSuffix '-staging'

        ...
    }
    debug {
        ...

        applicationIdSuffix '.debug'
        versionNameSuffix '-debug'

        ...
    }
}
productFlavors {
    paid {
        ...

        applicationIdSuffix '.free'
        versionNameSuffix '-free'

        ...
    }
    free {
        ...

        applicationIdSuffix '.staging'
        versionNameSuffix '-staging'

        ...
    }
}

そして、アプリ名も別にする必要があるので設定させる。アプリ名はAndroidManifestで変更できるが。
Alt text build.gradleでmanifestPlaceholdersを使ったらAndroidManifestで使用できる。たとえば、

1
manifestPlaceholders = [appName: "environment"];

上のように設定すると、AndroidManifestで

1
android:label="${appName}"

こういうふうに使うことができる。

Firebase Cloud Message in Android

FCM 使い方

Android

  • Firebaseにアプリを登録 Alt text
  • build.gradle編集

    • ルートレベルのbuild.gradle

    ルートレベルのbuild.gradleにgoogle-services プラグインを指定する。

    buildscript {
      // ...
      dependencies {
        // ...
        classpath 'com.google.gms:google-services:3.0.0'
      }
    }
    
    • モジュールのbuild.gradle

    モジュールのGradleファイル(通常は app/build.gradle)の末尾に apply plugin の行を追加し、Gradleプラグインを有効化する。
    必要なライブラリをdependenciesのところに書く。

    apply plugin: 'com.android.application'
    
    android {
      // ...
    }
    
    dependencies {
      // ...
      compile 'com.google.firebase:firebase-messaging:9.6.1'
    }
    
    apply plugin: 'com.google.gms.google-services'
    
  • アプリのマニフェストを編集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<service
    android:name=".MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

    ...

<service
    android:name=".MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>
  • google-services.json google-services.jsonファイルをFirebaseコンソールからダウンロードし、プロジェクトに入れ込む。 Alt text Alt text

Firebase Cloud Message in iOS

FCM (Firebase Cloud Message)

Firebaseとは

FirebaseはGoogleが運営しているmBaaS(Mobile Backend as a Service)であり、Backend as a Serviceとは一般的にアプリケーションに要求される色んな機能をまとめて提供してくれるサービスだ。

Firebaseの提供機能

  1. Analytics
  2. Cloud Messaging
  3. 認証
  4. Realtime Database
  5. Storage
  6. Hosting
  7. Remote Config
  8. Test Lab
  9. Crash Reporting
  10. Notifications
  11. App Indexing
  12. Dynamic Links
  13. Invites
  14. AdWords


詳しい情報はこちら。
https://firebase.google.com/features/

値段によってプランがそれぞれ違うけど、もちろん無料プランもある
Alt text

FCM 使い方

iOS (Swift基準)

  • Firebaseにアプリを登録
    Alt text Alt text Alt text
  • pod追加
    • まずcocoapodsをインストールする。
  • $ sudo gem install cocoapods
    $ pod setup
    
    • そしてPodfileを作る。
  • $ cd your-project directory
    $ pod init
    
    • Podfileが作られたらファイルの中に必要なpodを追加する。
  • pod 'Firebase/Messaging'
    
    • podをインストールして.xcworkspaceファイルを開いてプロジェクトを確認する。
  • $ pod install
    $ open your-project.xcworkspace
    
    • FirebaseコンソールでGoogleService-Info.plistをダウンロードしてプロジェクトに入れる。

Alt text Alt text

上記のリンクのとおり正しく進んだらp12ファイルを保存できるが、その保存したp12ファイルを下の画面にみえるところにアップすればセッティングは終わり。

Alt text

iOS, Androidの申請

初めに

アプリ申請の手順のまとめ。


使用機能

  • Android
    • Webview
    • Tab Layout
    • Push Notification
    • NativeのCamera, Album 機能
  • iOS
    • UIWebview
    • UITabbar
    • Push Notification
    • NativeのCamera, Album 機能

手順

  • Android
    • まず、下の写真のように新しいアプリを追加する。 Alt text
      Alt text
    • APKをアップロードする。 Alt text
    • 求められるものを手順に合わせて記入する。 Alt text
      • 必須項目
        • タイトル(30文字)
        • 簡単な説明(80文字)
        • 詳しい説明(4000文字)
        • スクリーンショット(2枚)
        • 高解像度アイコン(512x512, 32ビットPNG)
        • ヘッダー画像(横1,024x縦500, JPGまたは24ビットPNG)
        • アプリのタイプ
        • カテゴリ
        • コンテンツのレーティング
        • 新しいコンテンツ レーティング
        • メール(連絡先のメールアドレスを入力, このアドレスはアプリと一緒に公開される)
        • プライバシー ポリシー Alt text
          Alt text
          Alt text
          Alt text
    • コンテンツのレーティングを取る。 Alt text
      Alt text
    • 価格と販売/配布地域を設定する。
      • 必須項目
        • 広告を含む
        • コンテンツ ガイドライン
        • 米国輸出法 Alt text
    • 下の写真のように全部記入すればチェック表示が現れアプリを公開できるようになる。 Alt text

  • iOS
    • 新規アプリを登録する。 Alt text
      Alt text
    • Appleは必須項目とかは別に書いてないのでできるかぎり全部記入する方が良いと思う。 Alt text
    • Appleはスクリーンショットにかなり厳しい。
      • プレビュー, スクリーンショット
        • 5.5インチディスプレイ(App プレビュー(1枚) / スクリーンショット(5枚))
          • 72 dpi, RGB, flattened, no transparency
          • High-quality JPEG or PNG image file format
          • 1242 x 2208 pixels for hi-res portrait
          • 2208 x 1242 pixels for hi-res landscape
        • 4.7インチディスプレイ(App プレビュー(1枚) / スクリーンショット(5枚))
          • 72 dpi, RGB, flattened, no transparency
          • High-quality JPEG or PNG image file format
          • 750 x 1334 pixels for hi-res portrait
          • 1334 x 750 pixels for hi-res landscape
        • 4.0インチディスプレイ(App プレビュー(1枚) / スクリーンショット(5枚))
          • 72 dpi, RGB, flattened, no transparency
          • High-quality JPEG or PNG image file format
          • Any of the following sizes:
            • 640 x 1096 pixels for portrait (without status bar) minimum
            • 640 x 1136 pixels for portrait (full screen) maximum
            • 1136 x 600 pixels for landscape (without status bar) minimum
            • 1136 x 640 pixels for landscape (full screen) minimum
        • 3.5インチディスプレイ(App プレビュー(1枚) / スクリーンショット(5枚))
          • 72 dpi, RGB, flattened, no transparency
          • High-quality JPEG or PNG image file format
          • Any of the following sizes:
            • 640 x 920 pixels for hi-res portrait (without status bar) minimum
            • 640 x 960 pixels for hi-res portrait (full screen) maximum
            • 960 x 600 pixels for hi-res landscape (without status bar) minimum
            • 960 x 640 pixels for hi-res landscape (full screen) maximum

Alt text
Alt text
Alt text
Alt text
Alt text
iOSはプレビューとスクリーンショット以外にはそんなに難しくはないので早めに終わらせるが、審査はAndroidが1日で終わる反面iOSは一週間くらいかかるし、審査も厳しくてよくrejectされる

追記

  • Reject
    • 私の場合はセーフティーの項目にリジェクトされた。ソーシャル、コミュニティ系のアプリはユーザへのセーフティメカニズムが必要らしい。アップルからのメッセージは
      • 不快なコンテンツをフィルタリングするための方法。
      • 不快なコンテンツのユーザーのためのメカニズム。
      • 不正なユーザをブロックするためのメカニズム。
      • 開発者は、不快なコンテンツを削除し、問題のあるコンテンツを提供するユーザーを吐出することにより、24時間以内に好ましくないコンテンツレポートに行動しなければなりません。
  • 解決
    • 特定のユーザへのブロック機能を入れることで解決できた

Androidでカメラ&ギャラリーの使い方

はじめに


今回のinuckのアプリでファイルアップロードのためにカメラやギャラリーの機能を使って、Android側でも対応することになりましたので情報をまとめました。

説明


Android 4.4に変わってからWebviewはChromium基盤として変更されたため、WebviewにもMobile Chrome Browserと同じようなレベルのパフォーマンスを持って最新HTML5やCSS3のWeb標準をサポートし、V8 Javascript エンジンを乗せた最新バージョンに適用しました。
なので、Webviewのパフォーマンスについて色んな変更が発生され、ここで話すファイルアップロードについた部分も適用されました。 ここには4.4以外のバージョンをまとめます。

権限


AndroidManifest.xml

まず、Permissionを設定します。
カメラとギャラリーを使うためには
この三つが必要です。

1
2
3
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

この三つをAndroidManifestに入れます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.inuck.inuckapp">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>



    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:minSdkVersion="8"
        >
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".MyFirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
        <service
            android:name=".MyFirebaseInstanceIDService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
            </intent-filter>
        </service>
    </application>

</manifest>

Android 6.0になってからPermissionについて変わりまして6.0の前まではAndroidManifestに設定したら勝手に権限をとることになったんですが、6.0からは直接確認しないと権限が取れないところがありましてそのセッティングは相当めんどくさいので
私の場合はTedPermissionというライブラリを使いました。
ライブラリを使いたいなら下のようにbundle.gradleに設定したら良いです。 Alt text

4.4以外 (Android Version < 3.0  /  3.0 <= Android Version < 4.1  /  4.1 <= Android Version < 4.4  /  Android Version 5.0+)



共通で使うメソッド


imageChooser()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void imageChooser() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getContext().getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
            takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
        } catch (IOException ex) {
            // Error occurred while creating the File
            Log.e(getClass().getName(), "Unable to create Image File", ex);
        }

        // Continue only if the File was successfully created
        if (photoFile != null) {
            mCameraPhotoPath = "file:"+photoFile.getAbsolutePath();
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(photoFile));
        } else {
            takePictureIntent = null;
        }
    }

    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType(TYPE_IMAGE);

    Intent[] intentArray;
    if(takePictureIntent != null) {
        intentArray = new Intent[]{takePictureIntent};
    } else {
        intentArray = new Intent[0];
    }

    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

    startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
}


createImageFile()

1
2
3
4
5
6
7
8
9
10
11
12
13
private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File imageFile = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );
    return imageFile;
}


onActivityResult (int, int, Intent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void onActivityResult (int requestCode, int resultCode, Intent data) {
    if (requestCode == INPUT_FILE_REQUEST_CODE && resultCode == getActivity().RESULT_OK) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (filePathCallbackLollipop == null) {
                super.onActivityResult(requestCode, resultCode, data);
                return;
            }
            Uri[] results = new Uri[]{getResultUri(data)};

            filePathCallbackLollipop.onReceiveValue(results);
            filePathCallbackLollipop = null;
        } else {

            if (filePathCallbackNormal == null) {
                super.onActivityResult(requestCode, resultCode, data);
                return;
            }
            Uri result = getResultUri(data);

            Log.d(getClass().getName(), "openFileChooser : "+result);
            filePathCallbackNormal.onReceiveValue(result);
            filePathCallbackNormal = null;
        }
    } else {
        if (mFilePathCallback != null) mFilePathCallback.onReceiveValue(null);
        if (filePathCallbackNormal != null) filePathCallbackNormal.onReceiveValue(null);
        mFilePathCallback = null;
        filePathCallbackNormal = null;
        super.onActivityResult(requestCode, resultCode, data);
    }
}


getResultUri(Intent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Uri getResultUri(Intent data) {
    Uri result = null;
    if(data == null || TextUtils.isEmpty(data.getDataString())) {
        // If there is not data, then we may have taken a photo
        if(mCameraPhotoPath != null) {
            result = Uri.parse(mCameraPhotoPath);
        }
    } else {
        String filePath = "";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            filePath = data.getDataString();
        } else {
            filePath = "file:" + RealPathUtil.getRealPath(getContext(), data.getData());
        }
        result = Uri.parse(filePath);
    }

    return result;
}


バージョン別


1. Android Version 5.0+

1
2
3
4
5
6
7
8
9
10
public boolean onShowFileChooser(WebView webView,
                                 ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
    System.out.println("WebViewActivity A>5, OS Version : " + Build.VERSION.SDK_INT + "\t onSFC(WV,VCUB,FCP), n=3");
    if (filePathCallbackLollipop != null) {
        filePathCallbackLollipop.onReceiveValue(null);
    }
    filePathCallbackLollipop = filePathCallback;
    imageChooser();
    return true;
}


2. 4.1 <= Android Version < 4.4

1
2
3
4
5
public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
    Log.d(getClass().getName(), "openFileChooser : "+acceptType+"/"+capture);
    filePathCallbackNormal = uploadFile;
    imageChooser();
}


3. 3.0 <= Android Version < 4.1

1
2
3
4
5
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
    //System.out.println("WebViewActivity 3<A<4.1, OS Version : " + Build.VERSION.SDK_INT + "\t openFC(VCU,aT), n=2");
    filePathCallbackNormal = uploadFile;
    imageChooser();
}


4. Android Version < 3.0

1
2
3
4
5
6
7
8
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
    //System.out.println("WebViewActivity OS Version : " + Build.VERSION.SDK_INT + "\t openFC(VCU), n=1");
    filePathCallbackNormal = uploadMsg;
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType(TYPE_IMAGE);
    startActivityForResult(intent, INPUT_FILE_REQUEST_CODE);
}


テスト




1. Android 6.0



2. Android 5.1



3. Android 4.2

WebViewの作り方&JavascriptInterface

WebViewを作る

まず、プロジェクトを作ってみましょう。
下のようにアップ名とカンパニーのdomainを入力したらそれの組み合わせでPackage名が決められます。

ターゲットのデバイスとこのアプリで使える最小のSDKを選びます。

Activityを選びます。Activityは該当アプリの使い方によって選んだら良いです。

最後にActivityとLayoutの名前をついてFinishをクリックするとアプリが生成できます。

初めての画面です。MainActivityはアプリが起動されるときのStartPointになるところです。

次はLayoutですが、下のactivity_main.xmlというものがMainActivityのLayoutになり、MainActivityはこのLayoutを通してデバイスの画面に表せます。

Fragmentを使うためにこのLayoutはFrameLayoutにします。

では、Fragmentファイルを作ります。該当のPackageに右クリック-> New -> Java Class にして新しいJava Classを生成します。


上のように生成されたらそのClassの中身にコードを入れてみましょう。

public class MainFragment extends Fragment {
 
    private static final String TAG = MainFragment.class.getSimpleName();
 
    private WebView mWebView;
 
    public MainFragment() {
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);
 
        mWebView = (WebView) rootView.findViewById(R.id.fragment_main_webview);
        setUpWebViewDefaults(mWebView);
 
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
                return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
            }
        });
 
        if(mWebView.getUrl() == null) {
            mWebView.loadUrl(https://www.google.co.jp);
        }
 
        return rootView;
    }
 
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void setUpWebViewDefaults(WebView webView) {
        WebSettings settings = webView.getSettings();
 
        // Enable Javascript
        settings.setJavaScriptEnabled(true);
 
        // Use WideViewport and Zoom out if there is no viewport defined
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
 
        // Enable pinch to zoom without the zoom buttons
        settings.setBuiltInZoomControls(true);
 
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
            // Hide the zoom controls for HONEYCOMB+
            settings.setDisplayZoomControls(false);
        }
 
        // Enable remote debugging via chrome://inspect
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }
 
        // We set the WebViewClient to ensure links are consumed by the WebView rather
        // than passed to a browser if it can
        mWebView.setWebViewClient(new WebViewClient());
    }
}
このFragmentにWebViewを入れ込むのでLayoutを作ります。


下のようにWebViewのコードを入れてみます。

InternetのPermissionを取ります。

すると、

JavascriptInterface

WebViewを通してJSとNative間のConnectingができます。
上の設定を説明します。

getSettings().setJavaScriptEnabled(true);
これは文字そのとおりWebViewでJavascriptを使えるようになるものです。
addJavascriptInterface(new AndroidBridge(), “HybridApp”);
これはWebViewにJavascriptInterfaceを追加するもので、前の引数は実行するNative側のClassで、後ろの引数はJSから呼ぶときに使うキーです。
String userAgent = mWebView.getSettings().getUserAgentString();
            mWebView.getSettings().setUserAgentString(userAgent + “ inuckapp”);
次はUserAgentの設定です。UserAgentを設定したら特定なアプリだけで呼ばれ、他のアプリやMobileブラウザーで無駄に呼ばれるのを止められます。
続いてJSから呼ばれるClassをみてみると、

inuckapp()というメソッドを呼ぶことにします。ではinuckapp()の上にJavascriptInterfaceというAnotationを必ずつけなきゃいけないです。
Native側からもJSのメソッドを呼ぶことができます。それはloadUrl()を使って、javascript:[メソッド名]を引数として入れます。
次はサーバ側です。

navigator.userAgentを使ってどんなデバイスかを取得します。
Androidは「window.設定したキー.メソッド名」で簡単に呼べます。
さて、JS側のgoodMorning()を呼んでみます。

テストのため、ボタンを入れてみました。

ボタンをクリックすると、

JS側のメソッドが実行になり、Alertが出ました。
では、Native側からもデータを送ってみます。
メソッドの引数として渡すことができます。

すると、

Native側からJSにデータが送信されたのが確認できます。

Railsにおけるセッション管理

セッションとは

セッションを設定

class LoginsController < ApplicationController
  # ログインする
  def create
    if user = User.authenticate(params[:username], params[:password])
      # ユーザーIDをセッションに保存する
      session[:user_id] = user.id
      redirect_to root_url
    end
  end
end

セッションを取得

class ApplicationController < ActionController::Base
 
  private
    # 現在のユーザーを取得する
    # @_current_userが空の場合は、session情報をキーにしてDBから検索する
    def current_user
      @_current_user ||= User.find_by(id: session[:user_id])
    end
end

セッションを削除する

class LoginsController < ApplicationController
  # ログアウトする
  def destroy
    # @_current_userの値をnilにする
    @_current_user = nil
    reset_session
    redirect_to root_url
  end
end

Railsのセッション管理方法

CookieStore

ActiveRecordSessionStore

DalliStore

Gemの作り方

Gemとは

言語によってそれぞれ使用しやすい方式で作ったものを提供させています。それをライブラリ、またはフレームワークといいます。RailsがフレームワークだとしたらGemはライブラリです。つまり、必要な機能がある場合追加して使用したら良いです。
基本的にオープンソースとしてディストリビューションされていて、誰でも検索し、使用法を調べて使用したら良いです。

例え

devise

会員登録やログインシステムのためによく使われるGemのdeviseを例に取ってみます。
まず、https://rubygems.orgでdeviseを検索してみると、

上記のように色んなバージョンにできています。その中で自分がインストールしたいバージョンを選んでインストールしたら良いです。
次はgithubで検索してみると、


全てのGemはこのようにソースコードと使い方を公開しています。

 作り方

さて、本格的にGemの作り方を見てみましょう。
Gemの作り方は以外と簡単です。

bundle gem 「Gem名」

こういうふうにすると下のように作成ができます。

作成が終わったらディレクトリの中にlibフォルダーが確認できます。

この場合、hello_system_team.rbが本体になり、このGemをrequireすると、最初にこのファイルが呼ばれ、次にこのファイルでrequireしているものが呼ばれるという形になります。
version.rbは単純にGemのバージョン管理に必要なファイルです。
まず、hello_system_team.rbをみると、

require “hello_system_team/version”
 
module HelloSystemTeam
  # Your code goes here…
end
 
こうなっていますが
def self.exec
  puts “Hello System-Team!”
end
moduleの中にこれを入れます。上記のメソッドは例えなので実際に実行したいメソッドを入れたら良いです。 後はディレクトリの中にあるhello_system_team.gemspecの設定を変更します。
# coding: utf-8
lib = File.expand_path(‘../lib’FILE)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require ‘hello_system_team/version’
 
Gem::Specification.new do |spec|
  spec.name          = “hello_system_team”
  spec.version       = HelloSystemTeam::VERSION
  spec.authors       = [“jo”]
  spec.email         = [fantasticjyc@gmail.com]
 
  spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = “TODO: Put your gem’s website or public repo URL here.”
  spec.license       = “MIT”
 
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the ‘allowed_push_host’
  # to allow pushing to a single host or delete this section to allow pushing to any host.
  if spec.respond_to?(:metadata)
    spec.metadata[‘allowed_push_host’= “TODO: Set to ‘http://mygemserver.com’”
  else
    raise “RubyGems 2.0 or newer is required to protect against public gem pushes.”
  end
 
  spec.files         = git&nbsp;ls<span style="color:#0086b3"></span><span style="color:#ff3399">-</span>files&nbsp;<span style="color:#0086b3"></span><span style="color:#ff3399">-</span>z.split(“\x0”).reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = “exe”
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = [“lib”]
 
  spec.add_development_dependency “bundler”“~> 1.12”
  spec.add_development_dependency “rake”“~> 10.0”
  spec.add_development_dependency “rspec”“~> 3.0”
end
 
正しく変更できない場合はエラーが出てきます。僕の場合はsummary, description, homepage, metadataでエラーが出てきました。(泣)

そこまでできると、実行してみましょう。irbで実行してみたら下のようにうまくできます。
でも、複雑なGemになってくるとirbだけでは難しくなってきますのでその時ちゃんとテストを行うのが良いです。


無事にテストまでできればBuildとPushをします。


そして、Gemを検索してみると出てきますね!

他のプロジェクトでこのGemをインストールしたらGemに入っているメソッドを使うことができます。

参照

http://qiita.com/takashi/items/672781ed1b6255303381


iOS Webviewのまとめ

Project 作成

まずプロジェクトを作成します。


Webviewを入れ込む


プロジェクトが作成できると上の写真のようにMain.storyboardが作られてます。右側の下のところにあるobject liblaryでWebviewを探してview controllerに入れ込みます。

右側の上の方にあるassistant editorをクリックすると下のように画面が分けられてWebviewをオブジェクト化しやすくなります。


名前を作成したら、

その名前を持つオブジェクトが作れます。

Webviewの設定


上のようにWebviewを設定してURLをロードします。
delegateを指定しないとエラーになるので必ず入力します。そしてloadRequestメソッドを使ってURLを読み込みます。

Webviewのlayoutを指定して、

デバイスを選択し、runすると、

こういうふうに起動します。
単純にview controllerだけではなくてTabbar ControllerやNavigation Controllerを使うことも可能です。
今回はTabbar Controllerを使ってみます。

Tabbar Controllerの使い方


まず、右下のObject LibraryでTabbar Controllerを探して中に入れ込みます。

View Controllerの隣にある'->‘をEntryPointと言います。簡単にいうとアプリのスタートになるところを指定するPointです。

View ControllerにあるEntryPointをTabbar Controllerに移動します。

次は、Tabbar ControllerのSegueを該当View ControllerにつないでRelationshipを作ります。

そしたら上のようにView ControllerはTabbar Controllerの一つのItemとなります。

Tabbar Itemを編集して名前やImageも変えることができます。右のところで編集すると、

簡単に変わります。
デバイスで確認すると、

こう変わりました。

次は、Tabbarの色を変えてみます。Tabbarを選択し、右のところを編集すると、

色が変わります。
もう一度デバイスで確認すると、

こういうふうに変わるのを見ることができます。


個人的な意見では、Tabbar Controllerはそれぞれの機能を持てる画面を使用するNativeアプリにもっと合う気がします。Webviewを使うHybridアプリにはNativeアプリに使うことよりは効率性が高くないんじゃないかなと思います。