読者です 読者をやめる 読者になる 読者になる

Androidはワンツーパンチ 三歩進んで二歩下がる

Android卵プログラマーの記録ブログ

一般社団法人ちよだニャンとなる会さんに5千円寄付しました。Pokemon Go まだやってます。レベル28です。

Save Animal 雑感

こんにちは!
あっという間に一ヶ月経ってしまいました。
ここ2ヶ月ぐらいポケモンの世界にいたせいか、他のことが疎かになってきてるような気がしてヤバイと思っています。
開発をもっとやりたいのです。
密かに不安に感じているのですが最近物忘れもひどいので、いつまで自分は開発をやってられるのだろうか、
特にプログラミングでお金をいただくということは果たしていつまで出来るんだろう。
今が最後かもしれないのにこんなこと(ゲーム、ぐうたら、漫画、ネットを徘徊などなど)していていいのだろうか。
うわーん反省。ごめんなさい。
ちゃんとした人間になります!
って感じで一瞬大反省するもすぐ忘れてしまうということを繰り返しております(´-﹏-`;)

一応勉強はちびちびやってはいるのですがね。
↓ ここで記録しています。見えるかな?
studyplus.jp

何年か勉強の記録を付けてみて思ったのは
人間(特に自分)は弱い。だから習慣の力に頼るのだ。
ということです。
自分を責めるよりはまず15分だけ手を動かしたりすることですね。

とかなんとか言ってもとにかく理想通りにいかないですね。
私には何らかの強制力が働く方がいいのかもしれません。

ところで、今月は一般社団法人ちよだニャンとなる会さんに5千円寄付しました。
www.chiyoda-nyan.org

東京・千代田区で行政と連携・協力して「飼い主のいない猫」の問題に取り組んでいらっしゃいます。
行政もこの問題に取り組んでくれるってたまに見かけますが、嬉しくてうるっときます。
こうした取り組みが全国に全宇宙に広がっていってほしいですね。

さっき振込予約しました。
f:id:sakura_bird1:20160930214801p:plain


そして、Pokemon Goですが、トレーナーレベル28になりました。
いい運動になってますよ。

f:id:sakura_bird1:20160930215646p:plain

f:id:sakura_bird1:20160930215651p:plain


図鑑も142種類のうち138種類まで埋まりました。あと4種類。
f:id:sakura_bird1:20160930215653p:plain


最近せっかく10キロ歩いて卵を孵化してカイロスさんばかり生まれるのです。
f:id:sakura_bird1:20160930215657p:plain

一般社団法人ランコントレ・ミグノンさんに5千円寄付しました。ポケモンGoの進捗。

Save Animal

ズボラなのでブログというものをあまり書けず、今回も2つの主題を一つのエントリに詰め込むことになってしまいタイトルからして変です(;^ω^)

8月は一般社団法人ランコントレ・ミグノンさんに5千円寄付しました。
http://rencontrer-mignon.org/rencontrer-mignon.org

ランコントレ・ミグノンさんは動物の保護、シェルター運営、譲渡などを行っていらっしゃいます。
ほぼ日のサイトに糸井重里さんと代表の友森さんのインタビューが載っていました(๑•̀ㅂ•́)و✧

www.1101.com


これを書いているのは9月3日ですが、振り込みしたのは8月29日でした。

f:id:sakura_bird1:20160903155714p:plain


ここのところ時々ポケモンGoにハマっているので、支援活動に対するテンションが下がり気味です。
長い活動の中で身が入らない時期もあるのでチンタラしながらでもとにかく続けていこうと思います。

ところで、そのポケモンGoの進捗ですが最近レベル26になりました。
ジワジワ強くなっています。
普段のゲーム活動としては夜のウォーキング時のポケスト巡り、週1ぐらいでどこかの公園でポケモン捕獲、ちょっと課金ぐらいです。

f:id:sakura_bird1:20160903165058p:plain

f:id:sakura_bird1:20160903165152p:plain

f:id:sakura_bird1:20160903165203p:plain

f:id:sakura_bird1:20160903165210p:plain

f:id:sakura_bird1:20160903165214p:plain

f:id:sakura_bird1:20160903165158p:plain

戦闘員たち
f:id:sakura_bird1:20160903170333p:plain

一度ミニリュウを集めに上野の不忍池に行きました。
無事ミニリュウハクリューを20匹ぐらいかな?捕まえてカイリューに進化ましたが周りの人たちがミニリュウが出現するたびにダッシュするのでかなりびっくりしました。
独特の面白さを感じたのでまた行きたいです。

NPO法人東京キャットガーディアンさんに5000円寄付しました。そしてPokemon Goの日々な46歳

Save Animal その他

こんにちは!
今月はNPO法人東京キャットガーディアンさんに5000円寄付しました。
f:id:sakura_bird1:20160731171045p:plain

NPO法人 東京キャットガーディアン〜子猫の里親募集〜


大塚・西国分寺猫カフェ型開放型シェルターを拠点としつつ保護猫活動などの活動をなさっています。
譲渡総数5000頭を突破されているそうで、長いこと本当によく頑張ってこられたんだなあ、すごいなあと思っています。
今度大塚の猫カフェに行ったみたいです。



Pokémon GO がリリースされましたね。
www.pokemongo.jp

やり始めたら童心にかえったのか、結構ハマってしまいました。
ポケモンをゲットするために今までの2倍は歩くようになりました。
もう健康アプリという認識です😁
リアルな街歩きの最中に可愛らしいポケモンと出会えるのが嬉しくて。
プレーヤーのマナーが問題になったり何かとお騒がせですが、位置情報系のアプリが盛り上がっていくのが楽しみです。

↓近所で。感動しました↓
f:id:sakura_bird1:20160731171830p:plain

プレー開始2〜3日は若者にまじってポケモンをしていることが恥ずかしかったのですが、
今や全然気にせずやるようになってしまいました(●´ϖ`●)

↓ジムレベルが上がったタイミングで一瞬だけジムに自分のポケモンを置くことが出来ました!↓
f:id:sakura_bird1:20160731172530p:plain

今はレベル20になって、ポケモン個体値が気になり出しました。
個体値のいいポケモンを育てると強くなるらしいですよ。

↓このクサイハナは88-93%の高スコアなやつです。↓
f:id:sakura_bird1:20160731172814p:plain

面倒な計算はアプリでやっています。
Special Thanks to...
play.google.com

ポケモンゲットな日々が体重減少に結びつくよう願っています。

Android4.4のWebViewでopenFileChooserが動かない件の対処方法2つ JavascriptInterfaceを使う/Crosswalkを使う

Android

WebViewで表示しているページ上で、ユーザーがボタンを押したらローカルのイメージ一覧を表示するような処理があるとします。
そして、ボタンを押されたイベントをネイティブで受け取る→暗黙的Intentでギャラリーを呼び出しファイルを選択させるということをしようとするとします。


この場合、WebChromeClientを拡張してopenFileChooserメソッド又はonShowFileChooserメソッド(Lollipop以上のバージョンはメソッド名が変わっています)をoverrideして、ネイティブ側でイメージ一覧を表示する方法が考えられますが、Android4.4ではopenFileChooserメソッドが取り除かれていてメソッドが呼び出されないという不具合があります。

Issue 62220 - android - openFileChooser not called when <input type="file"> is clicked on android 4.4 webview - Android Open Source Project - Issue Tracker - Google Project Hosting

2013年からissueに上がっているのですが、公式では対応されていないようです。

WebViewはAndroid4.4でChromiumベースに変わっており、5.0以上は、Androidのプラットフォームとは切り離されてアップデートされるようになりました。これによりアップデートの進まないAndroidプラットフォームでもWebViewのセキュリティ上の脆弱性に素早く対応出来るようになりました。
Android 5.0未満を切り捨てることが出来れば一番いいと思うんですが2016/06現在なかなかそうはいかないです。
WebView for Android - Google Chrome


(ところでAndroid 4.3以前のWebViewについてはGoogleはサポートを終了しているようですが、4.4ってまだサポート対象なのでしょうか?軽いググりではよくわかりませんでした。)

issueやStackOverflowではopenFileChooserが動かない問題に対してだいたい2つの対処方法をおすすめされていて、当エントリでも試したことを書いておきます。
何か間違いを発見したりもっと良い方法を御存知でしたらご教示いただけますととても助かりますm(_ _)m

対処方法
1. openFileChooserでイベントを受け取るのを諦めてJavaScriptInterfaceを利用してWeb側からネイティブのメソッドを呼び出してもらう。
2.Chromiumエンジンで作られているWebViewのバックポートライブラリをアプリに組み込み、AndroidのWebViewの代わりに使用する。(ここではintel製のCrosswalkを使うものとします。)


利点と欠点
1.の方法

利点 欠点
・どのAndroidバージョンでもこの方法で動くはずなので、バージョン毎にコードを分けたりする必要がない。  ・外部ライブラリを使用しないのでアプリサイズが増えない。 ・WebページにAndroidのメソッドを呼び出すようにコーディングする必要があるので、Webページ側のコーディングが出来ない場合はこの方法は利用できない。  ・処理がWebページ側とネイティブ側にまたがっているのでコードを追いづらい。


2.の方法

利点 欠点
・4.4未満のバージョンでもChromiumエンジンの統一されたWebViewを使える。(ライブラリのメンテナンス状況によるがセキュリティ上のメリットも大きい) ・アプリサイズが大きくなる。(サンプルアプリを作ったら44Mを超えてしまいました。)  ・Google公式のサポート対象ではなくサードパーティ製のライブラリなので今後の開発状況が気になる。  ・WebViewをたくさん使うアプリなら導入のメリットがあると思うが、使用箇所が少ない場合は私には1の方が手軽に感じた。


どちらにも良し悪しあるので、プロジェクトによって合う方法を選べばいいと思います。
Multiple APKの機能を利用してAndroid5以降は公式のWebViewを使用してそれ以前のapkは2の方法を使うという手もあります。
開発コストがかかるので場合によりけりですが。



1.の方法の例

色んなところでおすすめされているサイト
Android Kitkat Webview image&nbsp;upload!codemumbai.wordpress.com
↑これを参考にして手元でテストしやすいように書き換えたサンプルです。
なお、サンプルではuploadボタンが付いていますがAndroidネイティブ側ではファイルをアップロードする実装はしていません。

  1. ローカルのassetsフォルダ配下にhtmlファイルを置いておいてWebViewで表示しています。
  2. Browse Galleryボタンを押すと、JavaScriptのメソッドからAndroidネイティブで定義しているメソッドを呼び出します。WebViewのaddJavascriptInterfaceメソッドを用いてバインディングしています。JavascriptInterfaceの使い方はこちらをごらんください。https://developer.android.com/guide/webapps/webview.html#BindingJavaScript
  3. サンプルプロジェクト全体はこちらからご覧になれます。

github.com


file:///android_asset/index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Website name</title>
    <script type="text/javascript">
        function browsepicture(){
            Android.openGallary();
        }
        function uploadfunction(){
            Android.uploadImage();
        }
        function setFileUri(uri) {
            document.getElementById('lbluri').innerHTML = uri;
        }

    </script>
</head>

<body>

<div data-role="page" id="pageone">
    <div data-role="header">
        <h1>Kitkat WebView</h1>
        <p><a href="https://codemumbai.wordpress.com/android-webview-image-upload-solved/">modified from android-webview-image-upload-solved</a>
        </p>
    </div>

    <div data-role="content">
        <input type="button" value="Browse Gallary" onClick="browsepicture()"/>
        <br>
        <label id="lbluri"></label>
        <br>
        <input type="button" value="Upload" onClick="uploadfunction()"/>
    </div>
</div>

</body>

</html>


MainActivity.java

package sakura_fish.com.exam.kitkatwebview;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.RuntimePermissions;

@RuntimePermissions
public class MainActivity extends AppCompatActivity {
    protected final int SELECT_PICTURE = 1;
    private Context mContext;
    private WebView mWebView;
    private boolean isImageSelected;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mContext = this;
        isImageSelected = false;
        mWebView = (WebView) findViewById(R.id.webview);
        settingWebView();
        mWebView.loadUrl("file:///android_asset/index.html");
    }

    @Override
    protected void onResume() {
        if (mWebView != null) {
            mWebView.onResume();
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mWebView != null) {
            mWebView.onPause();
        }
    }

    @Override
    protected void onDestroy() {
        if (mWebView != null) {
            mWebView.stopLoading();
            mWebView.setWebChromeClient(null);
            mWebView.setWebViewClient(null);
            mWebView.destroy();
            mWebView = null;
        }
        mContext = null;

        super.onDestroy();
    }

    @SuppressLint("SetJavaScriptEnabled")
    protected void settingWebView() {

        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setAllowFileAccess(true);
        // ここでバインディング
        mWebView.addJavascriptInterface(new WebAppInterface(), "Android");

        // openFileChooser not called on Android 4.4
//        mWebView.setWebChromeClient(new WebChromeClient() {
//            @SuppressWarnings("unused")
//            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
//                selectImage();
//            }
//
//            @SuppressWarnings("unused")
//            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
//                selectImage();
//            }
//
//            // For Android 4.1
//            @SuppressWarnings("unused")
//            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
//                selectImage();
//            }
//
//            // Android 5.0 +
//            @Override
//            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
//                selectImage();
//                return true;
//            }
//        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d(MainActivity.class.getSimpleName(), "requestCode:" + requestCode + " resultCode:" + resultCode);
        if (requestCode == SELECT_PICTURE && resultCode == Activity.RESULT_OK) {
            Uri selectedImage = data.getData();

            setFileUriToWebView(selectedImage.toString());

            InputStream input = null;
            try {
                input = mContext.getContentResolver().openInputStream(data.getData());
                if (input != null) {
                    // create image cache file
                    isImageSelected = true;
                    createPickCache(data.getData());
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private void setFileUriToWebView(String uriString) {
        mWebView.loadUrl("javascript:setFileUri('" + "uri : " + uriString + "')");
    }

    void checkStoragePermission() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            chooseImage();
            return;
        }
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            MainActivityPermissionsDispatcher.chooseImageWithCheck(MainActivity.this);
        } else {
            chooseImage();
        }
    }

    @NeedsPermission({Manifest.permission.READ_EXTERNAL_STORAGE})
    void chooseImage() {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.setType("image/*");
        startActivityForResult(Intent.createChooser(i, "File Chooser"), SELECT_PICTURE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        MainActivityPermissionsDispatcher.onRequestPermissionsResult(MainActivity.this, requestCode, grantResults);
    }

    private boolean createPickCache(@NonNull final Uri uri) {
        try {
            InputStream in = null;
            in = mContext.getContentResolver().openInputStream(uri);
            Bitmap bitmap = BitmapFactory.decodeStream(in);
            in.close();
            if (bitmap == null) {
                Log.e(MainActivity.class.getSimpleName(), "bitmap is null!");
                return false;
            }

            // サーバー送信用に画像を圧縮してテンポラリファイルに保存する
            float maxImageSize = 1500;
            ByteArrayOutputStream stream = new ByteArrayOutputStream();

            try {
                float ratio = Math.min(
                        maxImageSize / bitmap.getWidth(),
                        maxImageSize / bitmap.getHeight());
                int width = Math.round(ratio * bitmap.getWidth());
                int height = Math.round(ratio * bitmap.getHeight());

                Bitmap newBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
                newBitmap.compress(Bitmap.CompressFormat.JPEG, 70, stream);

                byte[] byteArray = stream.toByteArray();

                File file = new File(mContext.getCacheDir() + "cache.jpg");
                if (file.exists()) file.delete();
                FileOutputStream fo = null;
                fo = new FileOutputStream(file);
                fo.write(byteArray);
                fo.flush();
                fo.close();
                newBitmap.recycle();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                bitmap.recycle();
            }
            return false;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    private void sendImageToServer() {
        File file = new File(mContext.getCacheDir() + "cache.jpg");
        if (!file.exists()) {
            throw new IllegalStateException("No cache file!");
        }

        // TODO ここにサーバーに送信する処理を書く
        Toast.makeText(mContext, "TODO: 画像送信しました!", Toast.LENGTH_LONG).show();
        isImageSelected = false;
        setFileUriToWebView("");
    }

    // JavascriptInterface
    class WebAppInterface {

        @JavascriptInterface
        public void openGallary() {
            checkStoragePermission();
        }

        @JavascriptInterface
        public void uploadImage() {
            if (isImageSelected) {
                sendImageToServer();
            } else {
                Toast.makeText(mContext, "You need to select image!", Toast.LENGTH_LONG).show();
            }
        }
    }
}


2.の方法の例

CROSSWALKとは何ぞや?というところは、公式や参考サイト様をご覧になるのがいいと思います。

crosswalk-project.org

grandbig.github.io


当サンプルでは、CROSSWALKの埋め込みのViewを使います。
WebViewをCROSSWALKのViewに置き換えるイメージです。ファイルの選択はCrossWalkに任せています。

※Attention! こちらのサンプルではファイル選択→画像表示しかしていません。CrossWalkについて詳しい情報は公式サイトなどご覧ください。

サンプルの全体はこちらからご覧になれます。
github.com

/build.gradle

allprojects {
    repositories {
        jcenter()
        maven {
            url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2'
        }
    }
}

/app/build.gradle

dependencies {
    compile 'org.xwalk:xwalk_core_library:18.48.477.13'
}


file:///android_asset/index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Website name</title>
</head>

<body>

<div data-role="page" id="pageone">
    <div data-role="header">
        <h1>CrossWalk</h1>
        <p><a href="https://crosswalk-project.org/documentation/android/embedding_crosswalk.html">Android
            embded runtime</a>
        </p>
    </div>

    <div data-role="content">
        <input type="file">
    </div>
</div>

</body>

</html>


MainActivity.java

package sakura_fish.com.exam.crosswalk;

import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.ValueCallback;
import android.widget.ImageView;

import org.xwalk.core.XWalkUIClient;
import org.xwalk.core.XWalkView;

import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private XWalkView mXWalkView;
    private ImageView mImageView;
    private ValueCallback<Uri> mUploadMessage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mXWalkView = (XWalkView) findViewById(R.id.xwalkview);
        mImageView = (ImageView) findViewById(R.id.imageview);
        settingXWalkView();
        mXWalkView.load("file:///android_asset/index.html", null);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mXWalkView != null) {
            mXWalkView.pauseTimers();
            mXWalkView.onHide();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mXWalkView != null) {
            mXWalkView.resumeTimers();
            mXWalkView.onShow();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mXWalkView != null) {
            mXWalkView.onDestroy();
        }
    }

    protected void settingXWalkView() {
        mXWalkView.setUIClient(new MyUIClient(mXWalkView));
    }

    class MyUIClient extends XWalkUIClient {
        MyUIClient(XWalkView view) {
            super(view);
        }

        @Override
        public void openFileChooser(XWalkView view, ValueCallback<Uri> uploadFile, String acceptType, String capture) {
            Log.d(MainActivity.class.getSimpleName(), "openFileChooser");
            super.openFileChooser(view, uploadFile, acceptType, capture);
            mUploadMessage = uploadFile;
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d(MainActivity.class.getSimpleName(), "requestCode:" + requestCode + " resultCode:" + resultCode);

        if (mUploadMessage == null) return;
        Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;

        Bitmap bm = null;
        ContentResolver resolver = getContentResolver();
        try {
            bm = MediaStore.Images.Media.getBitmap(resolver, result);
            mImageView.setImageBitmap(bm);
            bm = null;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

参考サイト様
qiita.com
stackoverflow.com
stackoverflow.com
Embedding Crosswalk in Android Studio – diego.org
iti.hatenablog.jp
blog.asial.co.jp

特定非営利活動法人犬と猫のためのライフボート 犬と猫のためのライフボートさんに5千円寄付しました

Save Animal

www.lifeboatjapan.org

f:id:sakura_bird1:20160620204019p:plain

今月はライフボートさんに寄付しましたよ。
わんこもにゃんこも生命力強いですね。
たくさん生まれて死んでいく。
そりゃもー仕方がないことだけど、一匹でも幸せな子が増えるといいなと思います。
私も猫には本当に幸せをたくさんもらったので。
あきらめないでやってこうと思います。です!



ズボラなのでついでに告知
東新宿アプリ開発者もくもく会」という勉強会をやっています。
時々コワーキングスペースで集まって勉強などしているので、よかったら登録して下さい。
開発者とは言っていますが開発者じゃなくても大丈夫です😽

【Mac】git でコマンドラインから複数行のコミットメッセージを入力する

Mac git

git でコマンドラインから複数行のコミットメッセージを入力する方法を何回か調べてこれに落ち着いたので
忘れないようメモっておきます。

環境はMacで、ターミナルのbashからコマンドを入力します。

例えばこのようなコミットメッセージにしたい場合

"Introduce EventBus              ←1行目
                        ←2行目(改行のみ)
Ref. https://github.com/greenrobot/EventBus"  ←3行目

このように入力します。

$ git commit -m "Introduce EventBus

Ref. https://github.com/greenrobot/EventBus"

つまり1行目を入力したら
ダブルクオーテーションで閉じずに改行→改行→3行目入力→ダブルクオーテーションで閉じる
ようにします。

ちゃんと複数行になっています。
f:id:sakura_bird1:20160520024646p:plain


他にはこんな方法も試したりしました。
d.hatena.ne.jp

qiita.com

日本アニマルトラストさんに5000円寄付しました

Save Animal

こんにちは~。
最近はコワーキングスペースもくもく会を始めました。
集まってモクモクするだけなのでジャンルは問わないですが、一応アプリ開発者主体となりそうなので
東新宿アプリ開発者もくもく会」という名前にしました。
今のところまったり様子見ながらFacebookのみで告知してます。よかったら来てくださいね!
(現在はFacebook内で検索する必要があります。
https://www.facebook.com/groups/950255295071851/
 続きそうなら別の場所でも募集するかも。)
勉強ネタや手持ちの案件などコワーキングスペースで出来そうな作業をお持ちの方は是非〜(●^o^●)

表題の件ですが、日本アニマルトラスト動物の孤児院「ハッピーハウス」さんに5000円寄付しました。

www.happyhouse.or.jp



もう何年もずっと支援しているんですが、きっかけはこちらの団体で
「アニマル・セイブ・システム」という飼い主の死後ペットの生活を保証するしくみを見つけたからです。
でも今はあまり大々的に告知してないからあまり積極的にやっている策ではないのかもしれません。

f:id:sakura_bird1:20160519215338p:plain

寄付すると、領収書と「ハッピーハウス通信」という会報が送られてきます。
譲渡に至った保護ペットや活動内容について載せられていますよ!

最近は「寄付型自動販売機」なるものが登場しました。面白い(=^・^=)

f:id:sakura_bird1:20160519215642p:plain