チュートリアル

App Inventor 2の更新の手順を紹介します

MITからnb187aがリリースされたので日本語版でnb186aをnb187aに更新する手順を紹介します。自分でApp Inventor 2のサーバーを立ち上げたい人は参考にしてください。作業開始後nb187b、nb187cとリリースが続いたので、途中でバージョンが変わっています。今回のMITによる更新はかなり大幅なもので、更新の仕方が分かっていたわけではないので何度も行ったりきたりしながら手探りで作業を進めていて、15日間もかかった今までになく大変な更新作業だったので日付も入れて備忘録も兼ねています。「実録App Inventor 2日本語版更新」なのでこのまま手順をフォローするのはやめてくださいね。

利用者の方はApp Inventor 2日本語版が使えて当然と思っていると思いますが、更新作業はとても手間のかかる作業で、このような地道な作業をする人がいるためにApp Inventor 2日本語版が使えていることをご理解いただき、少しでもいいのでプロジェクト支援を検討してください。プロジェクトをご支援いただける方は下のボタンをクリックしてお願いします。

 

目次

    1. 2021年8月23日
      1. gitでnb187aをマージしてからCONFLICTを解決
      2. DockerコンテナでApp Inventor 2をビルド
      3. ビルド失敗原因の究明
    2. 2021年8月24日
    3. 2021年8月25日
    4. 2021年8月26日
      1. パソコンubuntu 18.04でデプロイを試す
    5. 2021年8月28日
      1. gitでnb187bをマージしてからCONFLICTを解決
    6. 2021年8月29日
      1. デプロイ前に動作確認
    7. 2021年8月30,31日
      1. 多言語変更部分調査と日本語追加
    8. 2021年9月1,2日
      1. ステージング環境にGUIデプロイ
      2. bootstrap.shにgcloudを追加
      3. ステージング環境でフルテスト
      4. 一部メニューを日本語化
    9. 2021年9月3日
      1. GitHubにプッシュ
    10. 2021年9月10日
      1. 本番環境にデプロイ
    11. 2021年9月12,13日
      1. 日本語未表示バグ

    2021年8月23日

    gitでnb187aをマージしてからCONFLICTを解決

    gitが使えるコンピュータに作業用フォルダーを作ってそこで以下のコマンドを実行します。日本語化プロジェクトではmacOS 10.15.7のiMacで作業しています。

    $ git clone https://github.com/tmsoftwareinc/appinventor-sources.git
    $ cd appinventor-sources/
    $ git remote add upstream https://github.com/mit-cml/appinventor-sources.git
    $ git checkout appinventorjapan
    $ git fetch upstream
    

    次にnb187aのコミットIDを見つけます。我々はSourceTreeを使いました。nb187aのコミットIDは 93a23aa575f74880d392f4032c3070bbbe365546 です。それではnb187aをマージしましょう。

    $ git merge 93a23aa575f74880d392f4032c3070bbbe365546
    Auto-merging appinventor/docs/html/reference/components/userinterface.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/userinterface.html
    Auto-merging appinventor/docs/html/reference/components/storage.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/storage.html
    Auto-merging appinventor/docs/html/reference/components/maps.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/maps.html
    Auto-merging appinventor/docs/html/reference/components/legomindstorms.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/legomindstorms.html
    Auto-merging appinventor/docs/html/reference/components/layout.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/layout.html
    Auto-merging appinventor/docs/html/reference/components/connectivity.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/connectivity.html
    Auto-merging appinventor/docs/html/reference/components/animation.html
    CONFLICT (content): Merge conflict in appinventor/docs/html/reference/components/animation.html
    Removing appinventor/blocklyeditor/src/msg/zh_tw/_messages.js
    Removing appinventor/blocklyeditor/src/msg/zh_cn/_messages.js
    Removing appinventor/blocklyeditor/src/msg/sv/_messages.js
    Removing appinventor/blocklyeditor/src/msg/ru/_messages.js
    Removing appinventor/blocklyeditor/src/msg/pt_br/_messages.js
    Removing appinventor/blocklyeditor/src/msg/pt/_messages.js
    Removing appinventor/blocklyeditor/src/msg/pl/_messages.js
    Removing appinventor/blocklyeditor/src/msg/nl/_messages.js
    Removing appinventor/blocklyeditor/src/msg/lt/_messages.js
    Removing appinventor/blocklyeditor/src/msg/ko_kr/_messages.js
    Removing appinventor/blocklyeditor/src/msg/it_it/_messages.js
    Removing appinventor/blocklyeditor/src/msg/hu/_messages.js
    Removing appinventor/blocklyeditor/src/msg/fr_fr/_messages.js
    Removing appinventor/blocklyeditor/src/msg/es_es/_messages.js
    Removing appinventor/blocklyeditor/src/msg/en/_messages.js
    Removing appinventor/blocklyeditor/src/msg/de/_messages.js
    CONFLICT (modify/delete): appinventor/blocklyeditor/src/language_switch.js deleted in 93a23aa575f74880d392f4032c3070bbbe365546 and modified in HEAD. Version HEAD of appinventor/blocklyeditor/src/language_switch.js left in tree.
    Removing appinventor/blocklyeditor/plovrConfigAI3.js
    Auto-merging appinventor/blocklyeditor/ploverConfig.js
    CONFLICT (content): Merge conflict in appinventor/blocklyeditor/ploverConfig.js
    Auto-merging appinventor/appengine/war/index.html
    CONFLICT (content): Merge conflict in appinventor/appengine/war/index.html
    Auto-merging appinventor/appengine/war/WEB-INF/appengine-web.xml
    Auto-merging appinventor/appengine/src/com/google/appinventor/client/languages.json
    Auto-merging appinventor/appengine/src/com/google/appinventor/client/Ode.java
    Auto-merging appinventor/appengine/src/com/google/appinventor/YaClient.gwt.xml
    CONFLICT (content): Merge conflict in appinventor/appengine/src/com/google/appinventor/YaClient.gwt.xml
    Auto-merging appinventor/appengine/build.xml
    CONFLICT (content): Merge conflict in appinventor/appengine/build.xml
    Automatic merge failed; fix conflicts and then commit the result.
    

    たくさんCONFLICTが出てきました。これをひとつずつ解決していきます。すべて解決できたらコミットします。

    DockerコンテナでApp Inventor 2をビルド

    App Inventor 2をビルドする環境を入れたdockerコンテナを使ってビルドします。日本語化プロジェクトでは以下のDockerfileで作ったコンテナを使っています。かなり以前に作ったのでソフトのバージョンは古いものになっています。

    
    FROM ubuntu:18.04
    
    # Build with
    #    docker build -t ai2 .
    RUN apt-get update
    RUN apt-get install -y curl unzip
    # Install prerequisites
    # python3 is need for i18n.py and pre-installed
    RUN apt-get install -y python
    RUN apt-get install -y openjdk-8-jdk
    RUN apt-get install -y lib32z1
    RUN apt-get install -y lib32ncurses5
    RUN apt-get install -y lib32stdc++6
    # Install other useful tools
    RUN apt-get install -y git vim ant sudo android-tools-adb
    # Install phantomJS
    RUN apt-get install -y phantomjs
    # Clean up
    RUN apt-get clean
    RUN apt-get purge
    # Add user
    RUN useradd -ms /bin/bash ubuntu
    # Install Google App Engine SDK
    RUN curl 'https://storage.googleapis.com/appengine-sdks/featured/appengine-java-sdk-1.9.72.zip' > /tmp/appengine.zip && unzip -d /home/ubuntu /tmp/appengine.zip && rm /tmp/appengine.zip
    # Install Google cloud SDK
    RUN curl 'https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-239.0.0-linux-x86_64.tar.gz' > /tmp/gcloud.tgz && cd /home/ubuntu && tar zxvf /tmp/gcloud.tgz 
    EXPOSE 8888 5037
    USER ubuntu
    ENV HOME /home/ubuntu
    ENV PATH $PATH:$HOME/google-cloud-sdk/bin
    
    

    dockerコンテナを起動します。

    docker restart コンテナ名
    docker attach コンテナ名
    

    コミットしたApp Inventor 2をコンテナにコピーします。macOSの~/Publicフォルダーをコンテナの/home/exportにマウントしているので簡単です。/home/ubuntu/appinventor-sourcesを作ります。

    それでは以下の手順でビルドしましょう。

    cd appinventor-sources/
    git submodule update --init
    cd appinventor/
    ant clean
    ant MakeAuthKey
    ant
    

    出力が延々と続きますが、結局以下のメッセージが出てビルドに失敗しました。

    BUILD FAILED
    /home/ubuntu/appinventor-sources/appinventor/build.xml:16: The following error occurred while executing this line:
    /home/ubuntu/appinventor-sources/appinventor/appengine/build.xml:581: Java returned: 137
    
    Total time: 4 minutes 7 seconds
    

    ここからエラーの原因を突き止める作業に入ります。まず確認のためにから英語版のnb187aをコンテナでビルドしてみます。

    rm appinventor-sources/
    git clone https://github.com/mit-cml/appinventor-sources.git
    cd appinventor-sources/
    git submodule update --init
    cd appinventor/
    ant clean
    ant MakeAuthKey
    ant

    マージした日本語版と同じエラーでビルドに失敗しました。

    YaClientApp:
        [mkdir] Created dir: /home/ubuntu/appinventor-sources/appinventor/appengine/build/extra
         [java] Compiling module com.google.appinventor.YaClient-dev
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to DEBUG or WARN to see all errors.
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource'
         [java]       Rebinding com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/editor/youngandroid/blockly.js
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport'
         [java]       Rebinding com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/utils/html5dnd.js
         [java]    Compiling 1 permutation
         [java]       Compiling permutation 0...
    
    BUILD FAILED
    /home/ubuntu/appinventor-sources/appinventor/build.xml:16: The following error occurred while executing this line:
    /home/ubuntu/appinventor-sources/appinventor/appengine/build.xml:581: Java returned: 137
    
    Total time: 3 minutes 16 seconds
    
    

    ビルド失敗原因の究明

    ここまでで分かったことは、

    1) nb186aまでは英語版、日本語版共にビルドに成功
    2) nb187aだと英語版、日本語版共にビルドに失敗
    3) MITではnb187aがビルドできている
    4) エラーメッセージからは原因はわからない

    これらの事実から仮説を立て検証します。

    1) nb187a日本語版に問題がある
       英語版でのビルドにも失敗しているので無し
    2) dockerコンテナの環境に問題がある
       可能性あり

    dockerコンテナの環境に問題があると言っても確実にビルドできる環境がわからないとどうしようもないので、今まで真面目に読む必要が無かったGitHub(https://github.com/mit-cml/appinventor-sources)のREADMEを読み直してみます。すると簡単にビルドするにはvagrantを使うといいよ、との記述が。nb187a英語版で早速やってみます。作業に使っているのはmacOS 10.15.7, 16GBメモリーの27インチiMac(2017)です。

    https://www.vagrantup.com/からvagrant_2.2.18_x86_64.dmgをダウンロードしてインストール
    https://www.virtualbox.org/wiki/DownloadsからVirtualBox-6.1.26-145957-OSX.dmgをダウンロードしてインストール
    vagrant plugin install vagrant-vbguest
    適当なフォルダー、例えば~/Documents/AI2_Repositories/tmp/を作って 
    mac> git clone https://github.com/mit-cml/appinventor-sources.git
    mac> cd appinventor-sources
    mac> vagrant up
    mac> vagrant ssh
    これでVirutalBoxのubuntu 18.04にログインできるのでビルドします
    ubuntu> ant

    以下のメッセージが出てビルドに失敗しました。

    BlocklyCompile:
         [java] Exception in thread "main" java.lang.IllegalArgumentException: File not found at: /vagrant/appinventor/blocklyeditor/../lib/closure-library/closure/goog
         [java] 	at com.google.common.base.Preconditions.checkArgument(Preconditions.java:88)
         [java] 	at org.plovr.Manifest.getInputs(Manifest.java:393)
         [java] 	at org.plovr.Manifest.getFiles(Manifest.java:369)
         [java] 	at org.plovr.Manifest.getFiles(Manifest.java:363)
         [java] 	at org.plovr.Manifest.getAllDependencies(Manifest.java:324)
         [java] 	at org.plovr.Manifest.getInputsInCompilationOrder(Manifest.java:193)
         [java] 	at org.plovr.Manifest.getCompilerArguments(Manifest.java:177)
         [java] 	at org.plovr.CompileRequestHandler.compile(CompileRequestHandler.java:94)
         [java] 	at org.plovr.cli.BuildCommand.runCommandWithOptions(BuildCommand.java:58)
         [java] 	at org.plovr.cli.BuildCommand.runCommandWithOptions(BuildCommand.java:31)
         [java] 	at org.plovr.cli.AbstractCommandRunner.runCommand(AbstractCommandRunner.java:41)
         [java] 	at org.plovr.cli.Command.execute(Command.java:47)
         [java] 	at org.plovr.cli.Main.mainWithExitCode(Main.java:56)
         [java] 	at org.plovr.cli.Main.main(Main.java:30)
    
    BUILD FAILED
    /vagrant/appinventor/build.xml:16: The following error occurred while executing this line:
    /vagrant/appinventor/build-common.xml:299: The following error occurred while executing this line:
    /vagrant/appinventor/blocklyeditor/build.xml:118: Java returned: 1

    2021年8月24日

    エラーメッセージを読んでみるとclosure-libraryが無いせいで失敗しているようです。これはREADMEの後の方に書いてありますが、git submodule update –init が必要みたいなので、やってみます。ちなみにdockerコンテナでビルドするときは git submodule update –init は手順に入っていました。

    ubuntu> git submodule update --init
    ubuntu> ant
    BUILD SUCCESSFUL
    Total time: 5 minutes 32 seconds

    やっとビルドに成功しました。これでdockerコンテナでビルドできない原因の調査ができます。

    GitHubからcloneしたApp Inventorには次のVagrantfileがあり、vagrant upした時にこのファイルからコンテナを作ります。ベースになっているのはubuntu/bionic64=ubuntu 18.04でdockerコンテナで使っているものと同じです。

    # -*- mode: ruby -*-
    # vi: set ft=ruby :
    
    # All Vagrant configuration is done below. The "2" in Vagrant.configure
    # configures the configuration version (we support older styles for
    # backwards compatibility). Please don't change it unless you know what
    # you're doing.
    
    Vagrant.configure("2") do |config|
    
      config.vm.box = "ubuntu/bionic64"
    
      config.vm.boot_timeout = 400
    
      config.vm.provider "virtualbox" do |v|
        v.name = "ForAppinventor2-bionic64"
        v.memory = "4096"
        v.customize ["modifyvm", :id, "--usb", "on"]
        # fix for slow network
        v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
        v.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
        v.customize ["modifyvm", :id, "--nictype1", "virtio"]
      end
    
      config.vm.provision :shell, path: "bootstrap.sh"
    
      config.vm.network :forwarded_port, guest: 8888, host: 8888
      config.vm.network :forwarded_port, guest: 9876, host: 9876
      config.vm.network :forwarded_port, guest: 9990, host: 9990
    
    end

    そして次のbootstrap.shを参照して様々なソフトをインストールします。

    #!/usr/bin/env bash
    
    dpkg --add-architecture i386
    
    # Install dependencies
    apt-get update
    apt-get upgrade -y
    apt-get install -y libc6:i386 libstdc++6:i386 glibc-doc:i386 gcc-5-base:i386 gcc-6-base:i386 libgcc1:i386 \
         openjdk-8-jdk zip unzip ant lib32z1 adb phantomjs
    
    # Install App Engine
    mkdir -p /opt/appengine
    cd /opt/appengine
    wget --no-verbose -O /tmp/appengine.zip https://storage.googleapis.com/appengine-sdks/featured/appengine-java-sdk-1.9.68.zip
    unzip -o /tmp/appengine.zip
    
    # Configure shell
    echo "export PATH=$PATH:/opt/appengine/appengine-java-sdk-1.9.68/bin" >> /home/vagrant/.bashrc
    echo "cd /vagrant/appinventor" >> /home/vagrant/.bashrc
    
    # Configure java
    update-java-alternatives -s java-1.8.0-openjdk-amd64
    
    # Make the auth key in advance
    cd /vagrant/appinventor
    sudo -u vagrant ant MakeAuthKey
    
    # Helper script for starting App Inventor dev server
    cat < /usr/local/bin/start_appinventor
    ant RunLocalBuildServer &> buildserver.log &
    BUILDSERVER=$!
    dev_appserver.sh -p 8888 -a 0.0.0.0 appengine/build/war
    kill -9 -- -$BUILDSERVER
    EOF
    chmod +x /usr/local/bin/start_appinventor

    ここでインストールしているのと同じソフトをインストールしたdockerコンテナを作ってみましょう。

    2021年8月25日

    ubuntu 18.04から新しくdockerコンテナを作り、bootstrap.shにあるコマンドを順に実行していきます。全部インストールできたら英語版nb187aを次の手順でビルドしてみます。

    git clone https://github.com/mit-cml/appinventor-sources.git
    cd appinventor-sources/
    git submodule update --init
    cd appinventor/
    ant clean
    ant MakeAuthKey
    ant

    結果は残念ながら失敗でした。

    YaClientApp:
        [mkdir] Created dir: /home/ubuntu/appinventor-sources/appinventor/appengine/build/extra
         [java] Aug 25, 2021 9:06:30 PM java.util.prefs.FileSystemPreferences$1 run
         [java] INFO: Created user preferences directory.
         [java] Compiling module com.google.appinventor.YaClient-dev
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to DEBUG or WARN to see all errors.
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource'
         [java]       Rebinding com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/editor/youngandroid/blockly.js
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport'
         [java]       Rebinding com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/utils/html5dnd.js
         [java]    Compiling 1 permutation
         [java]       Compiling permutation 0...
         [java]    Compile of permutations succeeded
         [java]    Compilation succeeded -- 225.904s
    
    BUILD FAILED
    /home/ubuntu/appinventor-sources/appinventor/build.xml:16: The following error occurred while executing this line:
    

    この後たまたまあったubuntu 18.04をインストールしたパソコンでビルドを試してみたところ、ビルドに成功しました。だとするとdockerの問題としか考えられなくなりました。そうこうしているうちにnb187bがリリースされます。これでdockerコンテナでもビルドできるようになったことを期待します。

    2021年8月26日

    ダメでした。英語版nb187bでもdockerコンテナでは全く同じエラーでビルドに失敗しました。

    ここまでくると精神的にも時間的にもきつくなってきたので、今まで長年使ってきたdockerコンテナを諦めてパソコンubuntu 18.04でデプロイまでやろうということになりました。この判断がビルド失敗の原因を発見することに間接的につながります。dockerコンテナを追求していたらデプロイできなかったと思います。ユーザーの皆さんごめんなさい、App Inventor 2日本語版は今後最新版に更新できなくなりました、理由はMITがわけのわからない更新をしたためです、ってメールで許してもらえるかな?とまで考えました。

    パソコンubuntu 18.04でデプロイを試す

    App Inventor 2はGoogleのクラウドサービスのApp Engineを使っていて、GUI部分はこのApp Engineにデプロイすることになります。デプロイするにはビルドできたら以下コマンドを使います。gcloudはGoogle Cloudコマンドラインツールです。https://cloud.google.com/sdk/docs/quickstart-linuxを参考にインストールします。

    gcloud app deploy appinventor-sources/appinventor/appengine/build/war/WEB-INF/appengine-web.xml

    はい、失敗しました。ここまでくると失敗しても、あっまた、って感じです。

    Beginning deployment of service [default]...
    ERROR: (gcloud.app.deploy) Cannot upload file
    [/tmp/tmp27ys7jo_/tmp6n000ge4/ode/4F75BDCDDD4332BEA20CD44DCAF54733.cache.js],
    which has size [46468038] (greater than maximum allowed size of
    [33554432]). Please delete the file or add to the skip_files entry in
    your application .yaml file and try again.
    

    このエラーは見覚えがあります。nb185aのデプロイの時に同じエラーが起きてかわしています。https://community.appinventor.mit.edu/t/deploy-app-inventor-failed-on-google-app-engine/2574/4 にかわし方があるので、これにしたがってソースを変更します。と思ってソースをみると既に変更されていました。もちろん条件付きで。いろいろソースを探っていくと、appengine/build.xmlとblocklyeditor/build.xmlに<property name=”release” value=”false”/>とあります。これですね。falseをtrueに変えてビルドです。これでデプロイできる、と思ったらさすがMIT、そう簡単には終わらせてくれません。

    YaClientApp:
        [mkdir] Created dir: /home/tam/Projects/AppInventor/AI2_Repositories/AI2_git_20210825/appinventor-sources/appinventor/appengine/build/extra
         [java] Compiling module com.google.appinventor.YaClient
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to DEBUG or WARN to see all errors.
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource'
         [java]       Rebinding com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/editor/youngandroid/blockly.js
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport'
         [java]       Rebinding com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/utils/html5dnd.js
         [java]    Compiling 34 permutations
         [java]       Compiling permutation 0...
    
    BUILD FAILED
    /home/tam/Projects/AppInventor/AI2_Repositories/AI2_git_20210825/appinventor-sources/appinventor/build.xml:16: The following error occurred while executing this line:
    /home/tam/Projects/AppInventor/AI2_Repositories/AI2_git_20210825/appinventor-sources/appinventor/appengine/build.xml:581: Java returned: 137

    vagrantならさすがにビルドできるはず、と思ってリリースモードでビルドしたところ、失敗。ここまでくるとMITに嫌がらせをされているんではないかという感じです。実際MIT以外でApp Inventor 2を動かせないようにしたんじゃないかと思いました。

    YaClientApp:
        [mkdir] Created dir: /vagrant/appinventor/appengine/build/extra
         [java] Aug 26, 2021 7:27:55 PM java.util.prefs.FileSystemPreferences$1 run
         [java] INFO: Created user preferences directory.
         [java] Compiling module com.google.appinventor.YaClient
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to DEBUG or WARN to see all errors.
         [java]    Ignored 3 units with compilation errors in first pass.
         [java] Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource'
         [java]       Rebinding com.google.appinventor.client.editor.youngandroid.BlocklyPanel.BlocklySource
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/editor/youngandroid/blockly.js
         [java]    Computing all possible rebind results for 'com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport'
         [java]       Rebinding com.google.appinventor.client.utils.HTML5DragDrop.HTML5DragDropSupport
         [java]          Invoking generator com.google.gwt.query.rebind.JsniBundleGenerator
         [java]             JsniBundleGenerator - importing external javascript: com/google/appinventor/client/utils/html5dnd.js
         [java]    Compiling 34 permutations
         [java]       Compiling permutation 0...
         [java]       Process output
         [java]          #
         [java]          # There is insufficient memory for the Java Runtime Environment to continue.
         [java]          # Native memory allocation (mmap) failed to map 60817408 bytes for committing reserved memory.
         [java]          # An error report file with more information is saved as:
         [java]          # /vagrant/appinventor/appengine/hs_err_pid24224.log
         [java]          [ERROR] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000db600000, 60817408, 0) failed; error='Cannot allocate memory' (errno=12)
         [java]       Process output
         [java]          [ERROR] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x000000008ee00000, 524288, 0) failed; error='Cannot allocate memory' (errno=12)
         [java]          #
         [java]          # There is insufficient memory for the Java Runtime Environment to continue.
         [java]          # Native memory allocation (mmap) failed to map 524288 bytes
         [java]       Process output
         [java]          Compiling
         [java]             Compiling permutation 5...
         [java]       Process output
         [java]          Compiling
         [java]             Compiling permutation 1...
         [java]       Process output
         [java]          #
         [java]          # There is insufficient memory for the Java Runtime Environment to continue.
         [java]          # Native memory allocation (mmap) failed to map 116391936 bytes for committing reserved memory.
         [java]          [ERROR] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000094300000, 116391936, 0) failed; error='Cannot allocate memory' (errno=12)
         [java]          # An error report file with more information is saved as:
         [java]          # /vagrant/appinventor/appengine/hs_err_pid24233.log
         [java]       Compiling permutation 8...
         [java]          #
         [java]          [ERROR] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000f3980000, 30932992, 0) failed; error='Cannot allocate memory' (errno=12)
         [java]          # There is insufficient memory for the Java Runtime Environment to continue.
         [java]          # Native memory allocation (mmap) failed to map 30932992 bytes for committing reserved memory.
         [java]          # An error report file with more information is saved as:
         [java]          # /vagrant/appinventor/appengine/hs_err_pid24238.log
         [java]       [WARN] Lost communication with remote process
         [java] java.io.EOFException
         [java] 	at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:3078)
         [java] 	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1618)
         [java] 	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
         [java] 	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
         [java] 	at com.google.gwt.dev.ExternalPermutationWorkerFactory$ExternalPermutationWorker.compile(ExternalPermutationWorkerFactory.java:154)
         [java] 	at com.google.gwt.dev.PermutationWorkerFactory$Manager$WorkerThread.run(PermutationWorkerFactory.java:74)
         [java] 	at java.lang.Thread.run(Thread.java:748)
         [java]          Compiling
         [java]             Compiling permutation 9...
         [java]       Compiling permutation 10...
         [java]          Compiling
         [java]             Compiling permutation 11...
         [java]       Compiling permutation 12...
         [java]          Compiling
         [java]             Compiling permutation 13...
         [java]       Compiling permutation 14...
         [java]          Compiling
         [java]             Compiling permutation 15...
         [java]       Compiling permutation 16...
         [java]          Compiling
         [java]             Compiling permutation 17...
         [java]       Compiling permutation 18...
         [java]          Compiling
         [java]             Compiling permutation 19...
         [java]       Compiling permutation 20...
         [java]          Compiling
         [java]             Compiling permutation 21...
         [java]       Compiling permutation 22...
         [java]          Compiling
         [java]             Compiling permutation 23...
         [java]       Compiling permutation 24...
         [java]          Compiling
         [java]             Compiling permutation 25...
         [java]       Compiling permutation 26...
         [java]          Compiling
         [java]             Compiling permutation 27...
         [java]       Compiling permutation 28...
         [java]          Compiling
         [java]             Compiling permutation 29...
         [java]       Compiling permutation 30...
         [java]          Compiling
         [java]             Compiling permutation 31...
         [java]       Compiling permutation 32...
         [java]          Compiling
         [java]             Compiling permutation 33...
         [java]       Compiling permutation 4...
         [java]          Compiling
         [java]             Compiling permutation 3...
         [java]       Compiling permutation 2...
         [java]          [ERROR] Compile failed
         [java]          java.lang.OutOfMemoryError
         [java]          	at java.io.RandomAccessFile.readBytes(Native Method)
         [java]          	at java.io.RandomAccessFile.read(RandomAccessFile.java:377)
         [java]          	at java.io.RandomAccessFile.readFully(RandomAccessFile.java:436)
         [java]          	at java.io.RandomAccessFile.readFully(RandomAccessFile.java:416)
         [java]          	at com.google.gwt.dev.util.DiskCache.readByteArray(DiskCache.java:77)
         [java]          	at com.google.gwt.dev.util.DiskCache.readObject(DiskCache.java:94)
         [java]          	at com.google.gwt.dev.jjs.UnifiedAst.getFreshAst(UnifiedAst.java:128)
         [java]          	at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler.compilePermutation(JavaToJavaScriptCompiler.java:322)
         [java]          	at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler.compilePermutation(JavaToJavaScriptCompiler.java:272)
         [java]          	at com.google.gwt.dev.CompilePerms.compile(CompilePerms.java:198)
         [java]          	at com.google.gwt.dev.CompilePermsServer.compilePermutation(CompilePermsServer.java:313)
         [java]          	at com.google.gwt.dev.CompilePermsServer.run(CompilePermsServer.java:276)
         [java]          	at com.google.gwt.dev.CompilePermsServer.main(CompilePermsServer.java:239)
         [java]             [ERROR] Out of memory; to increase the amount of memory, use the -Xmx flag at startup (java -Xmx128M ...)
         [java]       [ERROR] Error from external worker
         [java] java.lang.OutOfMemoryError
         [java] 	at java.io.RandomAccessFile.readBytes(Native Method)
         [java] 	at java.io.RandomAccessFile.read(RandomAccessFile.java:377)
         [java] 	at java.io.RandomAccessFile.readFully(RandomAccessFile.java:436)
         [java] 	at java.io.RandomAccessFile.readFully(RandomAccessFile.java:416)
         [java] 	at com.google.gwt.dev.util.DiskCache.readByteArray(DiskCache.java:77)
         [java] 	at com.google.gwt.dev.util.DiskCache.readObject(DiskCache.java:94)
         [java] 	at com.google.gwt.dev.jjs.UnifiedAst.getFreshAst(UnifiedAst.java:128)
         [java] 	at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler.compilePermutation(JavaToJavaScriptCompiler.java:322)
         [java] 	at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler.compilePermutation(JavaToJavaScriptCompiler.java:272)
         [java] 	at com.google.gwt.dev.CompilePerms.compile(CompilePerms.java:198)
         [java] 	at com.google.gwt.dev.CompilePermsServer.compilePermutation(CompilePermsServer.java:313)
         [java] 	at com.google.gwt.dev.CompilePermsServer.run(CompilePermsServer.java:276)
         [java] 	at com.google.gwt.dev.CompilePermsServer.main(CompilePermsServer.java:239)
         [java]          [ERROR] Out of memory; to increase the amount of memory, use the -Xmx flag at startup (java -Xmx128M ...)
         [java]       [ERROR] Unrecoverable exception, shutting down
         [java] com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
         [java] 	at com.google.gwt.dev.ExternalPermutationWorkerFactory$ExternalPermutationWorker.compile(ExternalPermutationWorkerFactory.java:157)
         [java] 	at com.google.gwt.dev.PermutationWorkerFactory$Manager$WorkerThread.run(PermutationWorkerFactory.java:74)
         [java] 	at java.lang.Thread.run(Thread.java:748)
         [java]       [ERROR] Not all permutation were compiled , completed (30/34)
    
    BUILD FAILED
    /vagrant/appinventor/build.xml:16: The following error occurred while executing this line:
    /vagrant/appinventor/appengine/build.xml:581: Java returned: 1

    でもちょっと待ってくださいよ。vagrantだとエラーメッセージが長くなっている。注目したのはここ

    [java]          # There is insufficient memory for the Java Runtime Environment to continue.
    [java]          # Native memory allocation (mmap) failed to map 60817408 bytes for committing reserved memory.
    

    メモリー不足?App inventorについてきたVagrantfileでは4GBメモリーを取っています。今までビルドできていたdockerコンテナは2GBメモリーを取っています。とりあえずVagrantfile 17行目を変更してメモリーを6GBにしてリリースビルド。

    v.memory = "4096"  ->  v.memory = "6144"

    コンテナから強制ログアウトされてしまいました。iMacで不要なソフトを止めてリリースビルドやり直すも、またメモリー不足エラー。Vagrantfile 17行目を変更してメモリーを8GBにしてリリースビルド。やっと成功。パソコンubuntu 18.04は物理メモリー3GBしか積んでいないのでビルド環境に使えないことが確定。

    vagrantにGoogle CloudコマンドラインツールをインストールしてApp Engineにデプロイ。ここではステージング環境のApp Engineにデプロイしているので本番環境に影響はありません。ようやくデプロイ成功!長い道のりでした。

    2021年8月28日

    gitでnb187bをマージしてからCONFLICTを解決

    英語版nb187bのデプロイに成功したので日本語版nb187bのデプロイを試します。前回と同じ日本語化する手順にしたがって日本語版nb187bを作ります。

    ここでもしかしたらdockerコンテナもメモリー増やせばビルドできるんじゃないかと思いつき、実行。とりあえず2GBから4GBに増やして開発ビルドしたところ、なんと成功!リリースビルドはやはり失敗。

    これ以上の作業は物理メモリー16GBのiMacでは無理なのでamazonで一昨日購入した32GB RAM($187)が届いてから続きの作業。使っている27インチiMac(2017)は最大64GBまでメモリー増設可能なのです。このiMacはamazonで$1500でApp Inventor 2の作業用に購入した改修品です。安くあげるために内臓HDはFusion Driveにしたので外付け1TB SSD($176)から起動してます。27インチiMacでメモリー16GB+1TB SSDにすると$2500くらいまで軽くいきますからね。

    iMacのメモリーを48GBに増設してdockerコンテナでリリースビルドできるメモリー量を探したところ、10GBでもダメなので思い切って16GBにしたところ無事成功!今まで2GBでビルドできていたことを考えるとMITはなんということをしてくれたんだという感じです。MITはお金があるだろうから16GBくらいどうってことないかもしれないけど。

    2021年8月29日

    デプロイ前に動作確認

    デプロイする前にdockerコンテナでローカル環境で動作確認することにします。次のコマンドでサーバーを起動するとローカルマシンの8888ポートにアクセスすることでApp Inventor 2が起動できます。

    appengine-java-sdk-1.9.72/bin/dev_appserver.sh --port=8888 --address=0.0.0.0  appinventor-sources/appinventor/appengine/build/war/  

    ブラウザを立ち上げて次のURLにアクセスします。が、メニューが日本語で表示されません。

    http://127.0.0.1:8888/??locale=ja_JP 

    言語選択ドロップダウンメニューにも日本語がありません。多言語対応部分が変更されているようです。どこが変更されたか調べているうちにあっという間に時間が過ぎ去りました。

    2021年8月30,31日

    多言語変更部分調査と日本語追加

    言語選択ドロップダウンメニューを定義しているのは appinventor/appengine/src/com/google/appinventor/YaClient.gwt.xml の終わりの方ですが、ここから日本語がなくなっていました。gitでマージした時のCONFLICT解決の時に失敗して消したようです。英語の次に追加し直しました。

    <!-- English language, independent of country -->
    <extend-property name="locale" values="en"/>
    <!-- Japanese -->
    <extend-property name="locale" values="ja_JP"/>

    それでもまだメニューが日本語になりません。appinventor/blocklyeditor/src/msg/ja_jp/_messages.js でかなりの量の日本語の翻訳をしていたんですが、gitでマージした時に他の言語の*/_messages.jsが無くなって代わりにmessage_*.json(例えばappinventor/blocklyeditor/src/msg/zh_tw/_messages.jsが無くなってappinventor/blocklyeditor/src/msg/ai_blockly/messages_zh_TW.json)が追加されています。ということでappinventor/blocklyeditor/src/msg/ja_jp/_messages.jsからappinventor/blocklyeditor/src/msg/ai_blockly/messages_ja.jsonを作ったところ、メニューが日本語になりました。JavaScriptファイルをJSONファイルに変換するのにはpythonを使いました。こんな感じです。

    ja_jp/_messages.js
    Blockly.Msg.DUPLICATE_BLOCK = '複製';
    Blockly.Msg.REMOVE_COMMENT = 'コメント削除';
    Blockly.Msg.ADD_COMMENT = 'コメント追加';
    
    messages_ja.json
    "Blockly.Msg.DUPLICATE_BLOCK": "複製",
    "Blockly.Msg.REMOVE_COMMENT": "コメント削除",
    "Blockly.Msg.ADD_COMMENT": "コメント追加",

    ついでながらappengine/build.xmlとblocklyeditor/build.xmlに<property name=”release” value=”false”/>を変更しなくてもリリースビルドはこれでできることがわかりました。

    ant -Drelease=true noplay 

    2021年9月1,2日

    dockerコンテナでローカル環境で動作確認です。すべての日本語が表示されているか見ていくと、日本語だけではなく韓国語や中国語が???になっている所があります。よくわからないので、vagrantでリリースビルドしてみます。ところが英語版nb187bはメモリー8GBでリリースビルドできたのに今回は失敗。

    [ERROR] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000f4780000, 34603008, 0) failed; error='Cannot allocate memory' (errno=12)
         [java]          #
         [java]          # There is insufficient memory for the Java Runtime Environment to continue.
         [java]          # Native memory allocation (mmap) failed to map 34603008 bytes for committing reserved memory.
         [java]          # An error report file with more information is saved as:
         [java]          # /vagrant/appinventor/appengine/hs_err_pid28463.log
         [java]       [WARN] Lost communication with remote process
         [java] java.io.EOFException
         [java] 	at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:3078)
         [java] 	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1618)
         [java] 	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
         [java] 	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
         [java] 	at com.google.gwt.dev.ExternalPermutationWorkerFactory$ExternalPermutationWorker.compile(ExternalPermutationWorkerFactory.java:154)
         [java] 	at com.google.gwt.dev.PermutationWorkerFactory$Manager$WorkerThread.run(PermutationWorkerFactory.java:74)
         [java] 	at java.lang.Thread.run(Thread.java:748)
         [java]       [WARN] Lost communication with remote process

    エラーメッセージから明らかにメモリー不足なので、vagrantのメモリーを増やしていって16GBでリリースビルド成功。これでローカル環境で動作確認したところ、正常に日本語が表示できました。

    ステージング環境にGUIデプロイ

    ステージング環境にGUIをデプロイしてみます。使うコマンドはこれです。無事成功しました。ステージング環境ですべてうまく日本語表示されました。

    bootstrap.shにgcloudを追加

    新規にvagrant upをした時にGoogle Cloudコマンドラインツールもインストールされるようにbootstrap.shに次の部分を追加しました。

    # Install gcloud
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    apt-get install -y apt-transport-https ca-certificates gnupg
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
    apt-get update && sudo apt-get install -y google-cloud-sdk
    apt-get install -y google-cloud-sdk-app-engine-java

    ステージング環境でフルテスト

    GUI部分は無事に動いたので、ビルドサーバーもステージング環境にデプロイしてすべての機能をテストします。まずはビルドサーバーのIPアドレス(XXX.XXX.XXX.XXX)をappinventor/appengine/war/WEB-INF/appengine-web.xml の87行の以下の部分に入力します。ビルドサーバー用のGoogle Cloud Compute EngineはUbuntu 18.04.3 LTSで動いています。

    <pproperty name="build.server.host" value="XXX.XXX.XXX.XXX:9990"></property>

    バージョン表示が綺麗になるように、gitコミットしてタグを付け直します。これをしないとバージョンがv187b_jp-1-g4c3702dcのようになります。git describe –dirty で表示されるのがGUIで表示されるバージョンです。できたらビルドします。

    git add /vagrant/appinventor/appengine/war/WEB-INF/appengine-web.xml; git commit -m "Added build server IP"
    git tag -d v187b_jp
    git tag -a v187b_jp
    git describe --dirty
    		v187b_jp
    
    ant clean
    ant -Drelease=true noplay

    ステージング環境にこのコマンドでGUIをデプロイしたら、ビルドサーバーのtarファイルを作ります。

    On vagrant
      cd appinventor/buildserver
      ant BuildDeploymentTar

    appinventor/build/buildserver/BuildServer.tar ができるので、これから次の手順で forbuild-nb187b.tgz を作りビルドサーバーにアップロードします。

    On iMac
    cd /Users/tam/Documents/AI2_Repositories/tmp
    mkdir for-BuildServer
    cp -p appinventor-sources/appinventor/build/buildserver/BuildServer.tar for-BuildServer/
    cp -p appinventor-sources/appinventor/misc/buildserver/launch-buildserver  for-BuildServer/
    tar cfz forbuild-nb187b.tgz for-BuildServer/

    ビルドサーバー用のCompute Engineにログインして、以下コマンドでビルドサーバーを起動します。

    tar xvfz forbuild-nb187b.tgz
    Cd for-BuildServer
    tar -xf BuildServer.tar
    ./launch-buildserver --maxSimultaneousBuilds 4

    一部メニューを日本語化

    結構前のリリースから気になっていたんですが、一部のメニューがまだ日本語化されていません。SettingsとかView Trashとかです。理由はApp Inventor 2自体が多言語に対応していなかったからなんですが、今回ソースを見直してみると多言語対応しているので日本語化しました。MITのApp Inventor 2ではまだ多言語対応していません。ハングルだとこんな感じですね。

    変更したファイルは appinventor/appengine/src/com/google/appinventor/client/OdeMessages_ja_JP.properties で、次の部分を追加しました。

    showExportAndroidApk=アンドロイドアプリ(.apk)
    
    showExportAndroidAab=アンドロイドアプリバンドル(.aab)
    
    trashButton=ゴミ箱を表示
    viewTrashTabName=ゴミ箱を表示
    trashButton=ゴミ箱を表示
    myProjectsButton=私のプロジェクト
    restoreProjectButton=元に戻す
    deleteFromTrashButton=ゴミ箱から削除
    showEmptyTrashMessage=ゴミ箱は空です
    
    settingsTabName=設定
    disableAutoload=プロジェクトの自動ロードを無効化
    enableOpenDyslexic=OpenDyslexicを有効化

    これで改めてビルドしてステージング環境にデプロイしてテストします。今度はGUI部分からHelloCodiプロジェクトを開いてビルド -> アンドロイドアプリ(.apk)でビルドしてみます。成功しました!

    2021年9月3日

    GitHubにプッシュ

    うまく動いたのでGitHubにプッシュします。タグはnb187b_jpにしました。後でユーザーの方にご指摘いただいたのですが、このプッシュの時に日本語ファイルの appinventor/blocklyeditor/src/msg/ai_blockly/messages_ja.json を入れ忘れました。今回の更新はまるでスムーズにいかなかったせいか、こうしたポカが多いですね。プッシュした後でGitHubからcloneしてテストビルドは2回もしたんですけど。

    これでデプロイしてようやく終わりかと思ったらMITからnb187cがリリースされました。でもGitHubにはプッシュされません。

    2021年9月10日

    いつまで経ってもnb187_cがGitHubにはプッシュされないのでMITに問い合わせたところ、nb187cはMIT内部変更なのでプッシュしないとか。それならそうと言ってよね、待ってる人もいるんだから。

    ということでGitHubにnb187c_jpタグをプッシュして完成。後は本番機デプロイ。

    本番環境にデプロイ

    作業自体はステージング環境デプロイと同じです。違うのはApp EngineのプロジェクトとビルドサーバーのIPアドレスだけです。デプロイ後GUIとビルド -> アンドロイドアプリ(.apk)が正常に動くことを確認します。ここで漏れがあるんですけどね。

    一応完了しました!nb187c_jpリリースです。

    2021年9月12,13日

    日本語未表示バグ

    ユーザーの方からtwitter経由で組み込みブロック内の命令が日本語になっていない旨、お知らせいただきました。とってもありがたかったです。日本語化プロジェクトは皆さんのご協力無しには運営できません。この場を借りて改めてお礼申し上げます。

    原因は appinventor/blocklyeditor/src/msg/ai_blockly/messages_ja.json を入れ忘れでした。ファイルを追加してコミット、GitHubにプッシュ、タグはnb187d_jpにしました。

    これで本当に完了、ならいいけどな。

    チュートリアル

    人工知能の機械学習モデルを使って画像を精度良く分類するアプリを作りましょう

    このアプリは人工知能画像分類アプリとは異なりエクステンションを使わないので機械学習モデルの変更が簡単です

    クリックしてアプリが動いているところを見てください

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”WhatisThis2″と名前を付けます。

    なぜエクテンションを追加しなくていいのか?

    tensorflow.jsを使うとブラウザで機械学習モデルを利用することができます。MITで開発されたエクステンションLookExtensionはtensorflow.jを利用していますが、機械学習モデルを変更するためにはにエクステンションをビルドし直さなければならないので、簡単にはモデルを変更できません。その上、このエクステンションで使っているモデルはサイズが小さいので精度がそれほど良くないため、本格的なアプリの開発にLookExtensionを使えません。日本語化プロジェクトではこの問題を解決するために、ブラウザで使用するJavaScriptを含んだウェブページを使用するだけで機械学習モデルを使った画像分類方法を開発しました。この方法ではイメージファイルをウェブページで読み込むために外部ウェブサイトを利用していますが、ウェブページ内のJavaScriptを変更するだけでモデルを容易に変更できます。

    デザイン編集

    レイアウトとコンポーネントの配置

    下図のようにユーザーインターフェースパレットからウェブビュー、テキストボックス、レイアウトパレットから横並びレイアウトを配置し、横並びレイアウトの中にユーザーインターフェースパレットから二つのボタンをドラッグアンドドロップし撮影ボタン、推論ボタンと名前を変更して配置します。見えないコンポーネントとしてメディアパレットからカメラ、タイマーを、接続パレットから二つのWebをドラッグアンドドロップしアップロード、ファイル削除と名前を変更します。テキストボックス1のヒントは「推論結果」に、撮影ボタンのテキストは「イメージ撮影」、推論ボタンのテキストは「推論」にします。

    クリックして拡大

    JavaScriptを含んだウェブページの追加

    以下のhtmlファイルを作成し、メディアの”ファイルをアップロード”ボタンをクリックしてアセットに追加します。ファイル名はindex.htmlにしてください。以下はこのファイルの簡単な説明です。

    行3:tensowflow.jsの読み込み
    行4:MobileNetの読み込み
    行10:撮影したイメージファイルの表示タグ
    行18-24:WebGL2判別関数
    行27-30:イメージ差し替え関数setimage()。App Inventorアプリから渡されるイメージファイルのURLをイメージタグにセットします
    行33-44:推論関数classify()。推論を行い結果をApp Inventorアプリに渡します
    行47-62:初期化関数app()。WebGL2の有無を判別してbackEndを設定したあと、MobileNetモデルを読み込みます

    <html>
    <head>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"></script>
    </head>
    <body>
    <div><input type="text" id="output" size="100" value=""></div>
    
    
    <img id="img" crossorigin src="https://img.appinventor.tmsoftwareinc.com/images/tfjs.png" width="80%"/>
    
    
    
    <script >
    let net;
    
    
    var supportsWebGL2 = ( function () {
    try {
    return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'webgl2' );
    } catch( e ) {
    return false;
    }
    } )();
    
    
    function setimage() {
    var appInventorInput = window.AppInventor.getWebViewString();
    document.getElementById('img').src = appInventorInput;
    }
    
    
    async function classify() {
    window.AppInventor.setWebViewString('');
    
    
    const imgEl = document.getElementById('img');
    
    
    const result = await net.classify(imgEl);
    const ret = result[0]['className'];
    // document.getElementById("output").value = ret;
    window.AppInventor.setWebViewString(ret);
    }
    
    
    async function app() {
    if(supportsWebGL2){
    document.getElementById("output").value = 'WebGL2 is supported';
    }else{
    document.getElementById("output").value = 'WebGL2 is not supported';
    tf.setBackend('cpu');
    }
    
    
    document.getElementById("output").value = 'モデル読み込み中..';
    
    
    // Read MobileNet
    net = await mobilenet.load();
    document.getElementById("output").value = 'モデル読み込み完了';
    }
    
    
    app();
    </script>
    </body>
    </html>

    ブロック編集機能を使用したプログラミング

    アプリの動作をプログラミングするには、 ブロック編集機能にアクセスする必要があります。 画面右上のブロック編集ボタンをクリックしてブロック編集機能に行きます。

    グローバル変数

    はじめにグローバル変数を定義します。組み込みブロック内の変数をクリックして”グローバル変数 変数名 を次の値で初期化”ブロックをドラッグアンドドロップして下図のように設定してください。productionはapkファイルをビルドする時は”真”、MIT AI2 Companionで実行する時は”偽”にします。imageHostはイメージファイルをアップロードするウェブサーバーです。タイマー待ち時間は推論結果を取得するまでの待ち時間をミリ秒で指定します。その他は一時的に使う変数です。

    クリックして拡大

    スクリーン初期化イベント

    “いつもScreen1初期化したら“ブロックに以下のブロックを組み込みます。最初の三つは一時グローバル変数を設定しています。順にアップロードしたイメージファイルのURL、イメージファイルのアップロードに使うスクリプトのURL、イメージファイルの削除に使うスクリプトのURLです。次のもしブロックはapkファイルをビルドする時とMIT AI2 Companionで実行する時はアセットのパスが異なるためにウェブビューのURLに異なるパスを設定しています。最後にタイマーの待ち時間を設定し、タイマーを無効にします。

    クリックして拡大

    撮影ボタンクリックイベント

    “撮影ボタンをクリックされたら“ブロックに“テキストボックス1テキスト“ブロックに空白テキストを組み込み、“呼び出すカメラ1撮影する“ブロックと、“推論ボタン有効を“ブロックにロジックの偽ブロックを組み込んだものを組み込みます。

    撮影終了後イベント

    “いつもカメラ1撮影終了後“ブロックでグローバル変数のphotoPathに撮影したイメージを設定し、アップロードWebのURLにグローバル変数のuploadURLを設定してアップロードWebにイメージをPostしてアップロードします。同時に推論ボタンを有効にします。

    アップロード結果取得イベント

    “いつもアップロードテキストに受け取ったら“ブロックで、アップロードが成功するとアップロードしたファイル名がレスポンスコンテントに返ってくるのでグローバル変数のimageNameにこのファイル名を設定し、ウェブビュー1のWebView文字列にイメージファイルのフルURLをセットして、setimage()関数を呼び出します。これによりアップロードしたイメージがスクリーンに表示されます。

    推論ボタンクリックイベント

    “推論ボタンをクリックされたら“ブロックに、“テキストボックス1テキスト“ブロックに空白テキストを組み込み、classify()関数を呼び出し推論を実行します。同時にタイマーを有効にします。

    タイマーイベント

    classify()関数を呼び出してからウェブビュー1のWebView文字列が返ってくるのには時間がかかるので、タイマーを使ってしばらく待っています。この時間はグローバル変数のタイマー待ち時間で設定していますが、500ミリ秒(0.5秒)です。“いつもタイマー1タイマー“ブロックで、まずタイマー1を無効にして、テキストボックス1テキストにウェブビュー1のWebView文字列を表示し、ファイル削除WebのURLにグローバル変数のremoveURLにファイル名を結合したURLを設定してからファイル削除Webをメソッドで呼び出してファイルを削除します。

    これで完成です。スマホでテストしてください。分類結果は英語で下部に表示されます。

     

    クリックして拡大

    ソースコードのダウンロード

    App Inventorでこのサンプルを使用したい場合は、 ソースコードをコンピュータにダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。

    PHPソースコード

    イメージファイルのアップロードと削除に使っているPHPソースコードです。ご自分のウェブサーバーを使うときに参考にしてください。ブラウザのセキュリティーのためにウェブサーバーはHeaderにAccess-Control-Allow-Origin “*” を返す必要があります。Apacheウェブサーバーの場合は Header set Access-Control-Allow-Origin “*” を設定します。

    upload.php

    <?php
    $data = file_get_contents('php://input');
    $tmpfname = tempnam("/var/www/------/images", "prefix");
    if (!(file_put_contents($tmpfname,$data) === FALSE)) echo basename($tmpfname); // file could be empty
    else echo "File xfer failed.";
    ?>

    remove.php

    <?php
    if(isset($_GET['fn'])) {
        $fname = "/var/www/------/images/" . $_GET['fn'];;
        if(file_exists($fname)) {
            echo "unlinked $fname";
            unlink($fname);
        }
    }
    ?>

    チュートリアル

    人工知能の機械学習モデルを使って画像を分類するアプリを作りましょう

    クリックしてアプリが動いているところを見てください

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”WhatisThis”と名前を付けます。

    エクテンションの追加

    https://appinventor.tmsoftwareinc.com/download/LookExtension.aix から日本語化プロジェクトでハックしたLookExtension.aixをダウンロードし、LookExtension.aixをエクステンションパレットからImport extensionをクリックしてインポートします。LookExtension.aixはMITで開発されたエクステンションですが機械学習モデルによる推論をアンドロイドデバイス上で行うために、オリジナルのLookExtension.aixは動作しないアンドロイドデバイスが多く、日本語化プロジェクト所有のアンドロイドデバイスでは動作確認ができませんでした。MITオリジナルのLookExtension.aixが動作確認されているアンドロイドデバイスはこちらです。日本語化プロジェクトでハックしたLookExtension.aixはMITオリジナルのLookExtension.aixが動作しないデバイスではバックエンドにCPUを使用することにより推論速度は遅くなるものの、多くのアンドロイドデバイスで動作するようになっています。MITオリジナル版が動作しなくて日本語化プロジェクトハック版が動作することが確認されているアンドロイドデバイスは今のところ、Google Nexus 5、Moto G Play、Samsung Galaxy Tab 4 8.0です。このエクステンションで使っているモデルはサイズが小さいので精度はそれほど良くありません。

    クリックして拡大

    デザイン編集

    レイアウトとコンポーネントの配置

    下図のようにユーザーインターフェースパレットから状況ラベル、レイアウトパレットから縦並びレイアウトを配置し、縦並びレイアウトの中にユーザーインターフェースパレットからウェブビュー、レイアウトパレットから横並びレイアウト、その中にはユーザーインターフェースパレットから撮影ボタンを配置します。見えないコンポーネントとしてエクステンションパレットLookExtensionとメディアパレットからカメラをドラッグアンドドロップします。状況ラベルのテキストは「準備中」に、撮影ボタンのテキストは「イメージ撮影」にします。Look1のプロパティのInputModeをImageに、WebViewerをウェブビュー1にするのを忘れないでください。忘れると動きません。

    クリックして拡大

    ブロック編集機能を使用したプログラミング

    アプリの動作をプログラミングするには、 ブロック編集機能にアクセスする必要があります。 画面右上のブロック編集ボタンをクリックしてブロック編集機能に行きます。

    エラーメッセージ

    はじめにグローバル変数を定義します。組み込みブロック内の変数をクリックして”グローバル変数 変数名 を次の値で初期化”ブロックをドラッグアンドドロップします。変数名は「エラーメッセージ」にしてください。次に組み込みブロック内のリストをクリックして”リストを作成”を1個ドラッグアンドドロップしてグローバル変数ブロックに組み込みます。

    “リストを作成”の左上にギアアイコンをクリックして要素をドラッグアンドドロップして要素が合計7個になるようにします。

    “リストを作成”を7個ドラッグアンドドロップして”リストを作成”ブロックに組み込みます。あとは数学にある数字とテキストにある文字列をドラッグアンドドロップして下図のようにします。

    “いつもLook1.Error”ブロックをドラッグアンドドロップして下図のように状況ラベルにエラーメッセージを表示するようにします。”結合する”ブロックは組み込みブロック内のテキストに、”ペア検索”ブロックは組み込みブロック内のリストにあります。”取得errorCode”ブロックは”いつもLook1.Error”ブロックのオレンジ色のerrorCodeをクリックすると取れます。

    撮影ボタンクリックイベント

    “撮影ボタンをクリックされたら“ブロックに“呼び出すカメラ1撮影する“ブロックと、“縦並び1見えるを“ブロックにロジックの偽ブロックを組み込んだものを組み込みます。

    “いつもカメラ1撮影終了後“ブロックに“呼び出すLook1ClassifyImageData“ブロックと状況ラベルに「推論中」と表示するブロックを組み込みます。”取得イメージ”ブロックは”いつもカメラ1撮影終了後”ブロックのオレンジ色のイメージをクリックすると取れます。

    Look1イベント

    “いつもLook1ClasifierReady“ブロックに”撮影ボタン有効を”ブロックにロジックの真ブロックを組み込んだものと状況ラベルに「推論中」と表示するブロックを組み込みます。

    “いつもLook1GotClasification“ブロックに“縦並び1見えるを“ブロックにロジックの真ブロックを組み込んだものと状況ラベルに結果リストの1番目を表示するブロックを組み込みます。”結果”ブロックは”いつもLook1GotClasification”ブロックのオレンジ色の結果をクリックすると取れます。

     
    これで完成です。スマホでテストしてください。分類結果は英語で上部に表示されます。
    クリックして拡大

    ソースコードのダウンロード

    App Inventorでこのサンプルを使用したい場合は、 ソースコードをコンピュータにダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。

    チュートリアル

    新型コロナ感染者数を地図を使って検索するアプリを作ります

    このアプリは実用的でGoogle Playで公開できるレベルのアプリです。ただし、新型コロナ関係のアプリはGoogle Playが受け付けないので、このアプリはこのままでは公開できません。

    更新履歴

    • 初版 2/11/2021

    使用しているテクニック

    1. RESTful API呼び出し
    2. JSONリストの扱い
    3. JSONリストのクイックソート
    4. GPS
    5. マップ
    6. TinyDB
    7. 手続き

    ソースコード(aiaファイル)のダウンロード(有料)

    チュートリアルを読んで手でアプリを入力すれば動くものが作れますが、入力する時間がもったいないという人のためにソースコードを2980円で販売しています。下の購入ボタンをクリックしてメールアドレス、クレジットカード番号、有効期限、CVCを入力すると、ダウンロードリンクがメールで送付されます。コンピュータにソースコードをダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。売り上げ金はプロジェクトの運営に必要なクラウドコンピュータ使用料金、テスト用スマホ購入費用、人件費などに当てられます。プロジェクトの発展のためにご協力ください。

    プログラムの構造(流れ)

    スポンサー企業のTM Software, Inc.ではアメリカ合衆国のジョンズ・ホプキンス大学が公開している全世界の新型コロナ感染者数のデータをデータベースに取り込み、1日の新規感染者数と人口10万人あたりの新規感染者数の7日間移動平均を計算し、RESTful APIを使用してデータを自由にダウンロードできるようにしています。このアプリではこのAPIを利用して1)最新の全国コロナ感染者数データを取得して新規感染者数の7日間移動平均の上位20都道府県にマークを表示、2)表示されている地図をクリックするとその場所の新規感染者数と累積感染者数を表示、3)地名を入力するとその場所にマップをズームしてその場所の新規感染者数と累積感染者数を表示、4)GPSを利用して現在地の新規感染者数と累積感染者数を表示、します。

    APIの呼び方と返される値

    緯度と経度からその都道府県のコロナ感染者数の時系列データを取得

    呼び方
    https://covid19api.tmsoftwareinc.com/api/v1/latlngs?lat=35.685750628984465&long=139.75407754498727
    返される値:JSONリスト
    [{“country”:”Japan”,”state”:”Tokyo”,”county”:””,”city”:””,”cases”:49539,”newcases”:822,”7rate”:4.19,”deaths”:532,”date”:”2020-12-18″},
    {“country”:”Japan”,”state”:”Tokyo”,”county”:””,”city”:””,”cases”:48717,”newcases”:678,”7rate”:3.95,”deaths”:532,”date”:”2020-12-17″},
    {“country”:”Japan”,”state”:”Tokyo”,”county”:””,”city”:””,”cases”:48039,”newcases”:460,”7rate”:3.84,”deaths”:522,”date”:”2020-12-16″},
    {“country”:”Japan”,”state”:”Tokyo”,”county”:””,”city”:””,”cases”:47579,”newcases”:305,”7rate”:3.73,”deaths”:513,”date”:”2020-12-15″},

    地名から緯度と経度を取得

    呼び方
    https://covid19api.tmsoftwareinc.com/v1/geos?location=東京
    返される値:JSON
    {“lat”:35.6803997,”lng”:139.7690174}

    最新の全国コロナ感染者数データを取得

    呼び方
    https://covid19api.tmsoftwareinc.com/api/v1/jabounds
    返される値:JSONリスト
    [{“state”:”Aichi”,”county”:””,”city”:null,”lat”:35.035551,”lng”:137.211621,”cases”:13535,”newcases”:238,”date”:”2020-12-18″,”sevenrate”:2.67},
    {“state”:”Akita”,”county”:””,”city”:null,”lat”:39.748679,”lng”:140.408228,”cases”:94,”newcases”:0,”date”:”2020-12-18″,”sevenrate”:0.06},
    {“state”:”Aomori”,”county”:””,”city”:null,”lat”:40.781541,”lng”:140.828896,”cases”:390,”newcases”:7,”date”:”2020-12-18″,”sevenrate”:0.35},
    {“state”:”Chiba”,”county”:””,”city”:null,”lat”:35.510141,”lng”:140.198917,”cases”:8663,”newcases”:148,”date”:”2020-12-18″,”sevenrate”:1.88},
    {“state”:”Ehime”,”county”:””,”city”:null,”lat”:33.624835,”lng”:132.856842,”cases”:372,”newcases”:2,”date”:”2020-12-18″,”sevenrate”:0.19},
    {“state”:”Fukui”,”county”:””,”city”:null,”lat”:35.846614,”lng”:136.224654,”cases”:337,”newcases”:0,”date”:”2020-12-18″,”sevenrate”:0.11},

    使用しているテクニックの詳細

    RESTful API呼び出し方法

    接続パレットにあるWebコンポーネントを使います。下のようにAPI呼び出しURLに必要な引数を結合してからWebコンポーネントのURLに設定し、WebコンポーネントのGetメソッドを呼び出します。

    Getメソッドは非同期で呼び出されるので、下の「いつもテキストに受け取ったら」を使って別途受け取る結果の処理を行います。

    JSONリストの扱い方法

    「いつもテキストに受け取ったら」で受け取る「レスポンスコンテント」をWebコンポーネントのJsonTextDecodeWithDictionariesメソッドに渡して、連想配列(dictionary)のリストに変換します。

    リスト内の要素を一つ取り出すときは「リスト内の順番の要素を取得」を、ループで全ての要素を取り出すときは「それぞれの項目リスト内の」を使います。

    取り出した要素は連想配列(dictionary)なので、キーを指定して値を取得します。

    JSONリストのクイックソート方法

    リストをQuick Sortアルゴリズムを使ってソートします。Dataにはソートするリストをleftにはソートするリストの最初の順番(通常は1)をrightにはソートするリストの最後の順番(通常はリストの要素数)をcolumnにはキーを指定します。

    以下がQuick Sortを行う手続きです。これは https://appinventorplus.wordpress.com/2017/04/12/quicksort-routine-for-your-app-inventor-apps/ 掲載の手続きを少し変更した物です。オリジナルの手続きはMIT App Inventor Galleryのここにあります。

    クリックして拡大
    クリックして拡大

    GPS

    GPSによる現在地の取得にはApp Inventor 2のセンサー関連パレットにある位置センサーコンポーネントを利用します。詳細はこちらを見てください。

    マップ

    マップはApp Inventor 2のマップ関連パレットにあるコンポーネントです。詳細はこちらを見てください。

    TinyDB

    TinyDBはApp Inventor 2のストレージパレットにあるコンポーネントです。詳細はこちらを見てください。

    プロジェクトを作成

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”casemap”と名前を付けます。

    デザイン編集

    レイアウトとコンポーネントの配置

    下図のようにレイアウトとコンポーネントを配置します。

    クリックして拡大

     

    1. 使用するイメージファイルを2個メディアにアップロードします。ファイルはCaseMapIcon.pngcaution-4-01.pngです。それぞれをクリックしてダウンロードしてください。CaseMapIcon.pngはScreen1のプロパティのアイコンに選びます。Screen1のプロパティのアプリ名は「新型コロナ感染者数マップ」、タイトルは「新型コロナ感染者数マップ」にします。
    2. 見えないコンポーネントとして接続パレットにあるWebを3個(ドラッグアンドドロップすると自動的にWeb1, Web2, Web3という名前になります)、センサーパレットにある位置センサー、ストレージパレットにあるTinyDBをドラッグアンドドロップします。
    3. 縦並びレイアウトを配置し「縦並び外側」と名前を変更したあと、その中に上から順番に横並び日付、縦並び内側、横並びヘルプ、マップ関連パレットからマップ1、横並びボタン、横並び状況、横並びデータソースを配置します。
    4. 次に順番にレイアウトの中にコンポーネントを配置していきます。横並び日付の中には日付という名前でラベルを、縦並び内側の中には横並び現在地(中に現在地という名前でラベル)、横並び感染率(中に感染率ラベル、感染率という名前で2個のラベル)、横並び新規(中に新規ラベル、新規感染者数という名前で2個のラベル)、横並び累積(中に累積ラベル、累積感染者数という名前で2個のラベル)を配置します。各ラベルのテキストは上の図の値にしてください。
    5. 横並びヘルプの中には注意イメージという名前でイメージと、ヘルプという名前でラベルを配置します。注意イメージの画像ファイルはcaution-4-01.pngを選びます。そのままだと大きいので、高さと幅は16ピクセルにします。ヘルプのテキストは「感染者数を知りたい場所で長押ししてください」にします。
    6. マップ1の中にはマップ関連パレットからマーカー1を配置します。マップ1の中心の緯度、経度は35.685750628984465, 139.75407754498727にすると皇居が中心になります。マーカー1の緯度、軽度も同じ値にしてください。
    7. 横並びボタンの中には場所テキストボックス、場所検索ボタン、現在地ボタン、全国ボタンを配置します。場所検索ボタン、現在地ボタン、全国ボタンのテキストは上の図の値にしてください。
    8. 横並び状況の中には状況ラベルを、横並びデータソースの中にはデータソースラベルを配置します。データソースラベルのテキストは「ジョンズ・ホプキンス大学のデータを使用しています」にします。

     

    ブロック編集機能を使用したプログラミング

    グローバル変数定義

    • コロナAPI: 緯度と経度からその都道府県のコロナ感染者数の時系列データを取得するAPIのURL https://covid19api.tmsoftwareinc.com/v1/latlngs?
    • コロナAPI2: 地名から緯度と経度を取得するAPIのURL https://covid19api.tmsoftwareinc.com/v1/geos?
    • コロナAPI3: 最新の全国コロナ感染者数データを取得するAPIのURL https://covid19.tmsoftwareinc.com/api/v1/jabounds
    • 初期状況メッセージ: 状況ラベルの初期値
    • 全国緯度: 全国表示する際のマップの中心緯度
    • 全国経度: 全国表示する際のマップの中心経度
    • 全国ズーム: 全国表示する際のマップのズームレベル
    • 初期緯度: マップの中心緯度の初期値
    • 初期経度: マップの中心経度の初期値
    • 初期ズーム: マップのズームレベルの初期値
    • 初期住所: 現在地ラベルの初期値
    • データ無し: データがない時のメッセージ
    • ヘルプ: ヘルプラベルの初期値=「は新規感染者数/10万人が高い都道府県を示していて、タップするとその数値を表示します。地名を入力して場所検索ボタンをクリックするとその場所に移動して感染者情報を表示します。感染者数を知りたい場所で長押しても感染者数情報を表示できます。」
    • 新規感染者数: 新規感染者数
    • 感染率: 感染率
    • マーカーリスト: 全国表示の際に感染率の高い場所に表示するマップ内マーカーのリスト
    • 感染率マーカー: 全国表示の際に感染率の高い場所に表示するマップ内マーカーの一時的な格納変数
    • 緯度: マップの現在の中心緯度
    • 経度: マップの現在の中心経度
    • 地図ズーム: マップの現在のズームレベル
    • JSONリスト: APIから取得したJSONリストの格納変数
    • JSON要素: JSONリストから取り出したJSON要素の格納変数
    • JSONリスト2: APIから取得したJSONリストの格納変数2
    • JSON要素2: JSONリスト2から取り出したJSON要素の格納変数
    クリックして拡大

    都道府県名翻訳用連想配列定義

    APIから返される都道府県名は英語なので、それを日本語に翻訳するために連想配列を定義してグローバル変数に格納しています。 

    クリックして拡大
    クリックして拡大

    手続き

    繰り返し使用するコードや複雑なコードは手続きで定義します。

    • JSONリストのクイックソート:こちらを見てください。
    • 感染者取得:緯度と経度からその都道府県のコロナ感染者数の時系列データを取得します。

    • 全マーカー表示、全マーカー非表示:マップ内マーカーをすべて表示したり、非表示したりします。

    スクリーン初期化に伴う作業など

    1. ヘルプラベル、状況ラベル、現在地ラベルを初期化します。
    2. TinyDBから前回使用した緯度、経度、ズームレベルを取得しグローバル変数の緯度、経度、ズームに設定します。初回起動時はグローバル変数の全国緯度、全国経度、全国ズームを使用します。
    3. グローバル変数の緯度、経度、地図ズームでマップをパンします。
    4. 緯度と全国緯度が等しくない場合は初回起動ではないので、マーカーは現在地に表示し感染者取得手続きを呼び出します。そうでない場合は初回起動なのでマーカーを非表示にし、縦並び内側も非表示にして都道府県ごとの感染者数情報を表示しません。
    5. 最新の全国コロナ感染者数データを取得するAPIを呼び出します。
    クリックして拡大

    緯度と経度からその都道府県のコロナ感染者数の時系列データを取得した時の処理

    感染者数の時系列データの取得にはWeb1コンポーネントを使用してAPIを呼び出しています。結果は非同期で返ってくるので、その際の処理を定義します。

    1. 返ってきた結果をグローバル変数のJSONリストに格納します。
    2. もしリストの長さが0ならば、縦並び内側を非表示にして都道府県ごとの感染者数情報を表示しません。状況ラベルにはデータ無しと表示します。
    3. そうでなければ、縦並び内側を表示にして状況ラベルは空白にします。引き続き以下の手順を実行します。
    4. JSONリストの最初の要素をJSON要素に格納します。
    5. もしJSON要素のcountryキーの値がJapanでは無いならば現在地ラベルにcountry+state+cityを結合した文字を、countryキーの値がJapanならばstateを都道府県名翻訳用連想配列を使って日本語に翻訳して現在地ラベルに表示します。
    6. JSON要素のdateキーの値を日付ラベルに表示します。
    7. もしJSON要素の7rate(新規感染者数の7日間移動平均)キーの値が-1(未定義)で無いならばグローバル変数の感染率にその値を格納し、そうでなければ「不明」を格納します。引き続き感染率ラベルにグローバル変数の感染率を表示します。
    8. もしJSON要素のnewcases(新規感染者数)キーの値が-1(未定義)で無いならばグローバル変数の新規感染者数にその値を格納し、そうでなければ「不明」を格納します。引き続き新規感染者数ラベルにグローバル変数の新規感染者数を表示します。
    9. 累積感染者数ラベルにJSON要素のcases(累積感染者数)を表示します。
    10. TinyDBに緯度、経度、ズームレベルを格納します。
    クリックして拡大

    地名から緯度と経度を取得した時の処理

    地名からの緯度と経度の取得にはWeb2コンポーネントを使用してAPIを呼び出しています。結果は非同期で返ってくるので、その際の処理を定義します。

    1. 返ってきた結果はリストでは無いのでグローバル変数のJSON要素に格納します。
    2. もしJSON要素のlatキーの値がnot foundで無いならばグローバル変数の緯度、経度にlatキーの値、lngキーの値を設定するとともにグローバル変数の地図ズームに初期ズームの値を設定し、グローバル変数の緯度、経度、地図ズームでマップをパンします。引き続きマーカー1を緯度、経度の場所に表示し、感染者数取得手続きを呼び出します。
    3. そうでなければ状況ラベルに「この場所は見つかりませんでした」と表示します。
    クリックして拡大

    最新の全国コロナ感染者数データを取得した時の処理

    最新の全国コロナ感染者数データの取得にはWeb3コンポーネントを使用してAPIを呼び出しています。結果は非同期で返ってくるので、その際の処理を定義します。

    1. 返ってきた結果をグローバル変数のJSONリスト2に格納します。
    2. もしリストの長さが0ならば、縦並び内側を非表示にして都道府県ごとの感染者数情報を表示しません。状況ラベルにはデータ無しと表示します。
    3. そうでなければ、JSONリスト2をsevenrate(新規感染者数の7日間移動平均)で降順にクイックソートします。
    4. JSONリスト2の1番目から20番目まで以下の通りループ処理します。
    5. ローカル変数のJSON項目にJSONリスト2の現在の要素を設定します。
    6. JSON項目のdateキーの値を日付ラベルに表示します。
    7. JSON項目のlatキーの値、lngキーの値の位置にマーカーを作成してグローバル変数の感染率マーカーに格納し、マーカーのイメージをcaution-4-01.pngに設定し、マーカーのタイトルにstateキーの値を都道府県名翻訳用連想配列を使って日本語に翻訳したものとsevenrateキーの値を結合した文字列を設定します。そしてマーカーの情報ボックスを有効にします。
    8. もしも1番目(新規感染者数の7日間移動平均が国内最大)ならばマーカーの情報ボックスを表示します。
    9. 作成したマーカーを上書きして消さないようにグローバル変数のマーカーリストに感染率マーカーを追加します。
    クリックして拡大

    マップ関連の処理

    マップ1が長押しされたら緯度、経度をグローバル変数の緯度、経度に格納し、マーカー1をその場所に移動して感染者数取得手続きを呼び出します。

    マップ1の境界が変わったら(ドラッグ、パン、ズームされたら)状況ラベルに初期状況メッセージを表示します。

    ボタンクリック処理

    場所検索ボタン

    1. 入力された地名から緯度、経度を取得するAPIを呼び出します。
    クリックして拡大

    現在地ボタン

    1. 位置センサーの緯度が0でなければ(位置情報が取れていれば)位置センサーの緯度、経度をグローバル変数の緯度、経度に、グローバル変数の地図ズームに初期ズームの値を設定し、グローバル変数の緯度、経度、地図ズームでマップをパンします。引き続きマーカー1を緯度、経度の場所に表示し、感染者数取得手続きを呼び出します。
    2. そうでなければ状況ラベルに「GPS準備中」と表示します。
    クリックして拡大

    全国表示ボタン

    1. グローバル変数の全国緯度、全国経度、全国ズームでマップをパンした後、全マーカーを表示し、1番目(新規感染者数の7日間移動平均が国内最大)のマーカーの情報ボックスを表示します。
    クリックして拡大

    チュートリアル

    我々は米国在住で日本国内のクラウドファンディングサイトが利用できませんので、米国のPatreonというクラウドファンディングサービスを利用しています。英語サイトなのでわかりにくいところもあるかと思い、ご支援方法をまとめました。

    Patreonページを表示

    https://www.patreon.com/appinventorjapan をクリックして下のようなPatreonページを表示します

    ログイン

    ページ右上 Login をクリックすると下のように表示されますので、Sign upからPatreonアカウントを作るか、Continue with Google, Continue with Apple, Continue with Facebookのいずれかをクリックしてそれぞれのアカウントを使ってログインしてください。

    初めてログインした時は居住地を聞かれますので、日本にお住いの場合は Japan 、その他の国の場合はお住いの国を選んで Submit をクリックします。

    レベルを選択して支援金お支払い方法の入力

    支援金額によって特典の異なる5種類のレベルがありますので、支援したいレベルを決めて、紫色のJoinボタンをクリックします。

    支払い方法入力画面が表示されますので、一番上のChoose what you pay(支援額)に支援金額を入力します。この支援金額は選択したレベルの月額費用の最低金額が表示されていますが、それ以上の金額を支払いたい場合はここで金額をご自分で入力できます。次に、カード情報を記入し Pay With Card ボタンをクリックして決済をします。CVVはカード裏面(アメリカンエクスプレスは表面)にあるセキュリティコードです。こちらをご覧ください。Postal Codeには郵便番号をハイフン無しで入力してください。PayPalアカウントをお持ちでクレジットカード決済をご希望ではない場合にはそちらを選択してください。

     

    これで完了です。お疲れ様でした。以降毎月1日に同じ金額が課金されます。

    チュートリアル

    スピナーもリストピッカーもいくつかの要素の中から一つを選ぶことが出来るコンポーネントです。ここではスピナーとリストピッカーの違いを調べてみます。

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”Spinner_ListPicker”と名前を付けます。

    デザイン編集

    ユーザインタフェースパレットから、 リストピッカーコンポーネントとスピナーコンポーネントをScreen1にドラッグアンドドロップします。レイアウトパレットから、 横並びコンポーネントを2個ドラッグアンドドロップし、その中にラベルコンポーネントを2個ドラッグアンドドロップして以下のように配置します。

    リストピッカーコンポーネントとスピナーコンポーネントのプロパティで”文字列から要素を生成”に”AAA,BBB,CCC,DDD,EEE”を入力しましょう。選択要素は”CCC”にします。

    リストピッカーコンポーネントとスピナーコンポーネントのプロパティはリストピッカーのボタン部分以外はほとんど同じです。

    ブロック編集機能を使用したプログラミング

    リストピッカーコンポーネントとスピナーコンポーネントで選択されている要素をラベルに表示します。まずはアプリ起動時に表示するように”いつもScreen1.初期化したら 実行する”ブロックで選択要素をラベルのテキストに設定します。選択要素を”CCC”にしているので”CCC”が表示されます。

    リストピッカーコンポーネントで要素が選択された時に選択された要素が表示されるように”いつもリストピッカー1.値が戻ったら 実行する”ブロックで選択要素をラベルのテキストに設定します。

    スピナーコンポーネントで要素が選択された時に選択された要素が表示されるように”いつもスピナー1.選択されたら 実行する”ブロックで選択要素をラベルのテキストに設定します。

    スピナーにはイベントは”いつもスピナー1.選択されたら 実行する”しかなく、あとはプロパティの取得、設定だけです。

    リストピッカーにはいろいろなイベントがありますが、”フォーカスされたら”と”フォーカスされなくなったら”は動いていません。他のイベントは動いています。

    結論

    機能的にはあまり差は無いのでデザインの好みで使い分ければ良いのでは無いでしょうか?リストピッカーはApp Inventor 2に固有のコンポーネントで、Androidにはありません。スピナーはAndroidにもあります。

     

    チュートリアル

    更新履歴

    • 初版 7/5/2019
    • 二版 7/17/2019: ボールのプログラミングに方位センサーの角度の説明を追加。ブラックホールのプログラミングの箇所「ヘディングを80度増やしている」→「ヘディングを180度増やしている」に修正。

    スマホのセンサーを使ったゲームを作りましょう

    スマホらしく方位センサーを使ったゲームを作ります。スマホを傾けて紫色のボールをころがしてゴールにボールを入れて得点します。途中にあるブラックホールに落とさないようにうまくコントロールする必要があります。

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”RollingBall“と名前を付けます。

    Screen1のタイトルを”ボールころがしゲーム“にします。ボール(3DPurpleBall.png)ブラックホール(3DBlackHole.png)5点ゴール(5pnt.png)10点ゴール(10pnt.png)20点ゴール(20pnt.png)の画像をそれぞれクリックしてパソコンにダウンロードします。3種類の音のファイル(success.mp3failure.mp3over.mp3)もクリックしてダウンロードしてください。 このチュートリアルでは段階的にアプリの組み上げ、テストを繰り返してアプリを作っていきます。ここで行うように段階的に作っていく方がバグの少ないアプリを作れます。

    ボール部分

    まずは紫色のボールを動かす部分からです。

    コンポーネントを追加

    描画とアニメーションパレットから、 キャンバスコンポーネントをビューアーにドラッグアンドドロップして名前を”フィールドキャンバス“にし、幅は”画面いっぱいに表示”、高さは”70%”にします。次に同じ描画とアニメーションパレットから、 イメージスプライトコンポーネントをフィールドキャンバスにドラッグアンドドロップして名前を”ボール“にし、先ほどパソコンにダウンロードした3DPurpleBall.pngをアップロードして画像に設定します。他のプロパティは下のように設定してください。

    • 有効:チェック
    • 描画間隔(ミリ秒):10
    • ヘディング:0、この値はプログラム内で変更します
    • 速さ:0.0、この値はプログラム内で変更します
    • 見える:チェック

    さらにセンサーパレットから、 方位センサーをビューアーにドラッグアンドドロップします。

    ブロック編集機能を使用したプログラミング

    アプリの動作をプログラミングするには、 ブロック編集機能にアクセスする必要があります。 画面右上のブロック編集ボタンをクリックしてブロック編集機能に行きます。

    方位センサーでボールを動かす

    ブロック編集機能の左側にある方位センサー1パネルをクリックして開き、”いつも方位センサー1.方位が変わったら”ブロックをドラッグアンドドロップし、ボール.ヘディングとボール.速さに方位センサー1.角度方位センサー1.Magnitudeを下図のようにはめ込みます。方位センサーの角度-180180の範囲の数値で、スマホ画面を水平から真右に傾けると0、左に傾けると-180、スマホの上側に傾けると90、手前に傾けると-90になります。方位センサーのMagnitude01の範囲の数値で、スマホ画面が水平なら0、垂直なら1になります。

    ここまででテストしてみましょう。Companionアプリをつないで実際にスマホを傾けてボールが動くことを確認してください。うまく動きますか?

    ゴール部分とテキスト部分

    コンポーネントを追加

    描画とアニメーションパレットから、 イメージスプライトコンポーネントを3個フィールドキャンバスにドラッグアンドドロップし、フィールドキャンバスの上部に並べます。そして名前を左から順番に”ゴール1“、”ゴール2“、”ゴール3“にします。そして10pnt.pngをアップロードして”ゴール1“の画像に、20pnt.pngをアップロードして”ゴール2″の画像に、5pnt.pngをアップロードして”ゴール3″の画像に、それぞれ設定します。それぞれのゴールが見えるようになったら下図のように並べてください。

    レイアウトパレットから、横並びコンポーネントをフィールドキャンパスの下にドラッグアンドドロップし、その中にユーザーインターフェースパレットから6個のラベルをドラッグアンドドロップして横に並べます。左から順番に名前を”ラベル1“、”点数“、”ラベル2“、”残り時間“、”ラベル3“、”最高得点“にします。そしてテキストを順番に点数、0、残り時間、秒、最高得点、0にします。

    レイアウトパレットから、横並びコンポーネントを”横並び1″の下にドラッグアンドドロップし、その中にユーザーインターフェースパレットからボタンコンポーネントを配置します。”横並び2″のプロパティで水平方向並びを中央揃えに幅を画面いっぱいに表示にします。あとはボタンの名前をリスタートに、テキストもリスタートにします。

    ユーザーインターフェースパレットからラベルコンポーネントを”横並び2″の下にドラッグアンドドロップし、名前をステータスに、テキストを”スマホを傾けてボールをコントロールして得点します。”に、フォントサイズを10にします。メディアパレットからコンポーネントを3個ビューアーにドラッグアンドドロップし、それぞれ成功音、失敗音、終了音と言う名前にします。再生ファイルにsuccess.mp3、 failure.mp3、 over.mp3の3個の音ファイルをアップロードして指定してください。ここまでで下図のようになります。

    ブロック編集機能を使用したプログラミング

    はじめにグローバル変数を定義します。組み込みブロック内の変数をクリックして”グローバル変数 変数名 を次の値で初期化”ブロックを5個ドラッグアンドドロップします。組み込みブロック内の数学から青い0ブロックをドラッグしてそれぞれのブロックにはめ込みます。変数名と数字を以下のように変更してください。

    組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”得点”に変更し、ブロックの左上にある青いアイコンをクリックして渡す引数を下図のように追加します。Xをクリックして引数の名前は”点数“にしてください。

    以下のようにブロックを組み上げます。成功音を鳴らした後、ボールを初期位置に戻し、点数をグローバル変数の得点に追加して現在の得点を表示しています。点数はこの”得点”手続きを呼ぶときに渡されています。

    ボールがいずれかのゴールに入ったら”得点”手続きを呼び出して点数を増やし、新しい点数を表示します。このためにブロック編集機能の左側にあるボールパネルをクリックして開き、”いつもボール.衝突したら 実行する”をドラッグアンドドロップします。衝突した相手が何点のゴールかによって点数が違うので、もしならばブロックを使います。この処理をしている間にボールが隣のゴールに入って重複得点しないように、はじめにボール.有効を偽にし、最後にボール.有効を真にします。

    ここまででテストしてみましょう。Companionアプリをつないで実際にスマホを傾けてボールを動かしてゴールに入れてください。点数はうまく増えていますか?

    タイマーの追加

    コンポーネントを追加

    いつまでもこのゲームをプレイできるといくらでも点数を取れてしまい、面白くありません。そこで時間制限を設けます。そのためにセンサーパレットのタイマーコンポーネントを使います。タイマーコンポーネントをビューアーにドラッグアンドドロップしてください。タイマーイベント間隔を1000ミリ秒に設定します。タイマー有効がチェックされていることも確認してください。

    ブロック編集機能を使用したプログラミング

    組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”ゲームオーバー”に変更します。後は以下のようにブロックを組んで手続きを作ってください。

    ブロック編集機能の左側にあるタイマー1パネルをクリックして開き、”いつもタイマー1.タイマー 実行する”をドラッグアンドドロップし、以下のようにブロックを組んでください。

    このままでは残り秒数が0秒なので、初期化ブロックを作ります。組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”初期化”に変更します。後は以下のようにブロックを組んで手続きを作ってください。

    次にブロック編集機能の左側にあるScreen1パネルをクリックして開き、”いつもScreen1.初期化したら 実行する”をドラッグアンドドロップし、この中に”呼び出す 初期化手続き”をはめ込みます。

    ブロック編集機能の左側にあるリスタートボタンパネルをクリックして開き、”いつもリスタート.クリックされたら 実行する”をドラッグアンドドロップし、この中に”呼び出す 初期化手続き”をはめ込みます。

    ここまででテストしてみましょう。ゲームオーバーになったらリスタートボタンをクリックしてもう一度プレイしてみてください。

    ブラックホール部分

    このままでは簡単にゴールにボールを入れられてしまうので、障害物としてブラックホールを2個置き左右に逆向きに往復するようにします。ボールがブラックホールに触ったらスタート地点までボールが戻ってしまうようにします。

    コンポーネントを追加

    描画とアニメーションパレットから、 イメージスプライトコンポーネントを2個フィールドキャンバスにドラッグアンドドロップします。そして名前をそれぞれ”ブラックホール1“、”ブラックホール2“にします。そして3DBlackHole.pngをアップロードして”ブラックホール1“、”ブラックホール2“の画像に設定します。描画間隔はいずれも10ミリ秒、ヘディングは”ブラックホール1“が0(右向きに移動)、”ブラックホール2“が180(左向きに移動)、速さは”ブラックホール1“が4、”ブラックホール2“が3、Yは”ブラックホール1“が60、”ブラックホール2“が150、Xはいずれも130に設定してください。

    ブロック編集機能を使用したプログラミング

    ブロック編集機能の左側にあるブラックホール1パネルをクリックして開き、”いつもブラックホール1.端に到達したら 実行する”をドラッグアンドドロップし、以下のようにブロックを組んでください。”ブラックホール2″もまったく同じに組み上げます。ブラックホールが端に到達したらヘディングを180度増やしているので折り返して動き続けます。

    次にボールがブラックホールに落ちた時の処理をプログラムします。

    組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”ブラックホールに落下”に変更します。後は以下のようにブロックを組んで手続きを作ってください。

    すでにプログラムしてある”いつもボール.衝突したら 実行する”ブロックに以下の赤枠ブロックを追加します。

    ここまででテストしてみましょう。少しは難しくなりましたか?

    最高得点の記録

    コンポーネントを追加

    最高得点を記録するにはアプリが終了しても得点を記憶しておく仕組みが必要です。App Inventor 2 にはそんな時に使えるTinyDBと言うコンポーネントがあります。ストレージパレットから、 TinyDBコンポーネントをビューアーににドラッグアンドドロップして、名前を”最高得点DB“に変更します。

    ブロック編集機能を使用したプログラミング

    すでにプログラムしてある”いつもScreen1.初期化したら 実行する”ブロックに以下の赤枠ブロックを追加します。最高得点は”score”というタグでTinyDBに保存してあることにして、アプリ起動時にその値を取得して表示しています。

    次に最高得点が得た時に保存するコードです。すでにプログラムしてある”ゲームオーバー”手続きに以下の赤枠ブロックを追加します。

    最後にセンサーがあるかチェック

    このゲームでは方位センサーを使っているので、方位センサーの無いスマホではプレイできません。そこで方位センサーがあるかどうかをチェックして無ければエラーメッセージを表示します。”いつもScreen1.初期化したら 実行する”ブロックを以下のように変更してください。

    これで完成です。スマホでテストしてください。面白いですか?
     

    ソースコードのダウンロード

    App Inventorでこのサンプルを使用したい場合は、 ソースコードをコンピュータにダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。

    チュートリアル

    簡単なゲームを作りましょう

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”MoleMash”と名前を付けます。

    デザイン編集

    Screen1のタイトルを”もぐらたたき”にします。モグラの画像をここをクリックしてパソコンにダウンロードします。

    コンポーネントを追加

    描画とアニメーションパレットから、 キャンバスコンポーネントをビューアーにドラッグアンドドロップします。その下にユーザーインターフェースパレットから、ラベルボタンをドラッグアンドドロップします。

    キャンバス1のプロパティの高さと幅を300ピクセルに変更し、ラベルは名前を”点数”にテキストを”点数:0″に設定します。ボタンは名前を”リセットボタン”にテキストを”リセット”に設定します。

    さらにメディアパレットから、 コンポーネントをビューアーにドラッグアンドドロップします。コンポーネントを使ってモグラがたたかれた時にスマホをバイブレートさせます。

    タイマーコンポーネントを追加

    モグラは定期的にジャンプして位置を変えます。このためにセンサーパレットのタイマーコンポーネントを使います。タイマーコンポーネントをビューアーにドラッグアンドドロップしてください。タイマーイベント間隔を700ミリ秒に設定してこの間隔でモグラが動くようにします。タイマー有効がチェックされていることも確認してください。

    イメージスプライトを追加

    イメージスプライトを使ってモグラを描画します。イメージスプライトはキャンバスの中を移動する画像です。イメージスプライトには速さ、ヘディング(進む方向)、描画間隔を指定でき、タッチされたかを検出することもできます。

    描画とアニメーションパレットから、 イメージスプライトコンポーネントをキャンバス1にドラッグアンドドロップして名前をモグラにします。引き続きプロパティを以下のように設定してください。

    • 画像:先ほどダウンロードしたmole.pngをアップロードして使います。
    • 有効:チェック
    • 描画間隔(ミリ秒):使わないので500のままにしてください
    • ヘディング:0、これも使いません
    • 速さ:0.0、これも使いません
    • 見える:チェック
    • 高さ、幅:自動

    ブロック編集機能を使用したプログラミング

    アプリの動作をプログラミングするには、 ブロック編集機能にアクセスする必要があります。 画面右上のブロック編集ボタンをクリックしてブロック編集機能に行きます。

    手続きの追加

    繰り返して使用するコードを手続きとして定義することができます。ここでは以下の二つの手続きを作ります。

    • モグラを動かす:キャンバス上ランダムな場所にモグラを動かします。
    • 点数更新:点数を更新します。

    モグラを動かす

    組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”モグラを動かす”に変更します。ブロック編集機能の左側にあるモグラパネルをクリックして開き、”設定 モグラ X”と”設定 モグラ Y”を実行の横にはめ込みます。さらに下図にあるようにランダム分数や掛け算、引き算などのブロックを組み込みブロック内の数学からドラッグしてきて組み上げます。キャンバス1の高さ、幅、モグラの高さ、幅も使用します。

    点数更新

    まず、点数を保持するためにグローバル変数を定義します。組み込みブロック内の変数をクリックして”グローバル変数 変数名 を次の値で初期化”ブロックをドラッグアンドドロップし、変数名を”点数”に変更した後、組み込みブロック内の数学から青い0ブロックをドラッグしてはめ込みます。次に、組み込みブロック内の手続きをクリックして”渡す 手続き 実行”ブロックをドラッグアンドドロップし、”手続き”を”点数更新”に変更します。ブロック編集機能の左側にある点数パネルをクリックして開き、”設定 点数 テキスト”を実行の横にはめ込んだ後、組み込みブロック内のテキストから”結合する”ブロックをドラッグして空白テキストと”取得 グローバル 点数”を使って下図のように組み上げます。

    タイマーの追加

    タイマーを使ってモグラが動き続けるようにします。ブロック編集機能の左側にあるタイマー1パネルをクリックして開き、”いつもタイマー1.タイマー 実行する”をドラッグアンドドロップし、組み込みブロック内の手続きをクリックして”呼び出す モグラを動かす“ブロックを下図のようにはめ込みます。

    モグラ.タッチされたらハンドラーの追加

    モグラがタッチされたら点数を1点増やし、新しい点数を表示します。このためにブロック編集機能の左側にあるモグラパネルをクリックして開き、”いつもモグラ.タッチされたら 実行する”をドラッグアンドドロップします。そして下図のようにグローバル 点数を1増やす、音1を使ってバイブレートする、”呼び出す 点数更新“、”呼び出す モグラを動かす“、をはめ込みます。

    点数リセット

    点数をリセットするには下図のように組み上げます。

     
    これで完成です。スマホでテストしてください。面白いですか?
     

    ソースコードのダウンロード

    App Inventorでこのサンプルを使用したい場合は、 ソースコードをコンピュータにダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。

    チュートリアル

    郵便番号を検索するアプリを作ります

    このチュートリアルは初心者用ではありません。わかりにくい点などありましたらTwitter(@AppInventorJPN)またはFacebookページFacebookグループでお知らせください。随時更新します。標準のApp Inventor 2日本語版にSQLiteエクステンション、Zipエクステンション、Fileエクステンションを追加して使用しています。また、簡単なSQL言語を使用しています。App Inventor 2日本語版でどこまでのアプリを作れるかの実験だと考えてください。

    更新履歴

    • 初版 6/11/2019
    • 二版 6/23/2019: Android 8.0.0以降のファイル書き込み許可をサポートするために更新。更新部分は都度その旨表示
    • 三版 6/28/2019: フィードバックに基づき修正。更新部分は都度その旨表示
        • いつもAfterUnzip内ImportDatabaseブロックのコメントを削除(aiaファイル内)

    ソースコードのダウンロード

    ソースコードを見ながらこのチュートリアルを読んだ方がわかりやすいと思います。ここをクリックして、コンピュータにソースコードをダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。  

    プログラムの構造(流れ)

    SQLite自体はAndroidに標準装備です。

    1. あらかじめSQLiteの全国郵便番号データベースファイル(約12万件)を作成しておき、このファイルのzip圧縮ファイルをアセットとしてアプリに同封します。App Inventorではアセットとしてアップロードできるファイルのサイズは5MB程度が上限なので、圧縮しています。全国郵便番号データベースファイルの圧縮前ファイルサイズは8.5MB、圧縮後ファイルサイズは2.3MBです(三版修正:圧縮前->圧縮後)。万が一、アセットにファイルが無い時あるいは圧縮ファイルサイズが5MBを超えた時に備え、指定サーバーからzip圧縮ファイルをダウンロードして使用する機能も持っています。
    2. zip圧縮データベースファイルを解凍後、SQLiteにインポートし、データベースから都道府県のリストを作成してスピナーで表示します。
    3. 都道府県が選択されたら指定都道府県内の市町村リストを作成してスピナーで表示します。
    4. 市町村が選択されたら指定市町村内の町域リストを作成してスピナーで表示します。
    5. 町域が選択されたら定都道府県内指定市町村内指定町域の郵便番号を表示します。

    郵便番号データベースファイルの作成(App Inventorは使いません)

    https://www.post.japanpost.jp/zipcode/dl/roman-zip.html より全国一括のzip圧縮データファイルをダウンロードします。解凍後、表計算ソフトでローマ字列を削除し、代わりに通し番号の列を追加します。また文字コードがShift JISなので、UTF-8に変換してからcsvで保存します。

    SQLiteデータベースのテーブル構造を定義し、以下のようにテーブルを作成するSQL文を作ります。

    BEGIN TRANSACTION;
    CREATE TABLE IF NOT EXISTS “japanzip” (
        “id” INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
        “zip” TEXT,
        “prefs” TEXT,
        “city” TEXT,
        “street” TEXT
    );
    COMMIT;

    SQLite GUI管理ツール(たとえばDB Browser for SQLite)を使って、テーブルを作成後csvファイルをインポートしてデータベースを作成します。ここではjapanzip.sqliteという名前でデータベースを作成し、japanzip.zipというファイルに圧縮しました。japanzip.zipはここからダウンロードしてください

    プロジェクトを作成

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”ZipJapan”と名前を付けます。

    エクテンションの追加

    1. GitHub からAppInventor extension for SQLiteをローカルパソコンにクローンし、org.bennedum.SQLite.aixをエクステンションパレットからImport extensionをクリックしてインポートします。
    2. https://puravidaapps.com/zip.php  からTaifunZip extension (aix file)をダウンロードし、com.puravidaapps.TaifunZip.aixをエクステンションパレットからImport extensionをクリックしてインポートします。
    3. https://puravidaapps.com/file.php  からTaifunFile extension (aix file)をダウンロードし、com.puravidaapps.TaifunFile.aixをエクステンションパレットからImport extensionをクリックしてインポートします。

    デザイン編集

    レイアウトとコンポーネントの配置

    下図のように縦並びレイアウトを配置した後、その中に横並びレイアウト、ラベル(タイトル、初期化ラベル、住所ラベル、郵便番号ラベル)、スピナー(選択ドロップダウン)、テキストボックス(郵便番号)、ボタン(初期化ボタン、リセット)を配置します。選択ドロップダウン、初期化ラベル、初期化ボタン、住所ラベル、郵便番号ラベル、郵便番号はプロパティの見えるからチェックを外してください。さらに、見えないコンポーネント類としてエクテンションに追加したSQLite、 TaifunZip,、TaifunFileと接続パレットにあるWeb、ストレージパレットにあるファイルをドラッグアンドドロップします。最後にファイルをアップロードをクリックしてjapanzip.zipをアップロードします。

    初期化ラベル、初期化ボタン、ファイルコンポーネントは二版にて追加。

     

    ブロック編集機能を使用したプログラミング

    グローバル変数定義

    三版でsreetを削除

    • asset_fn: アセットにアップロードしたデータベースファイルのパス
    • unzipped_fn: 解凍したデータベースファイルのパス
    • unzip_fn: 解凍するためにはストレージ領域にファイルをコピーする必要があります。コピー先のデータベースファイルのパス。
    • downloaded_fn: ダウンロードしたデータベースファイルのパス
    • download_url: データベースファイルをダウンロードするURL
    • 郵便番号:
    • 町域選択: 町域選択メッセージ
    • 市町村選択: 市町村選択メッセージ
    • 都道府県選択: 都道府県選択メッセージ
    • app_dbfile: SQLiteデータベースファイル名
    • prefs: 選択済み都道府県格納用
    • city: 選択済み市町村格納用
    • column: 現在表示中のコラム名
    三版でdownload_urlのフルURLを追加

    都道府県リスト手続き

    郵便番号検索の入り口である都道府県リストの表示は繰り返し行われるので手続きを作っています。やっていることはSQLiteデータベースをopenして”select distinct(prefs) from japanzip”と言うSQL文を実行して重複しない都道府県名を取得し、その結果からスピナーを作っています。後はグローバル変数とラベルの初期化作業をしています。二版で住所ラベル.見えるを追加しています。

    スクリーン初期化に伴う作業など

    Screen1.アクセス許可と初期化ボタン.クリックされたらの処理が二版にて大幅に変更されています。

    1. スクリーンが初期化したらファイルの読み書き許可を求めます。2回目以降の起動時はデータベースファイルが存在するので、直接都道府県リスト手続きを呼び出し、郵便番号検索を始めます。
    2. ファイルの読み書き許可が得られてなおかつデータベースファイルが存在しない場合は、以下のブロックで実際にzipjapan.txtと言う名前のダミーファイルの書き込みを行ってから初期化ラベルと初期化ボタンを表示します。Android 8.0.0以降ではファイルが実際に書き込まれるまでファイルの読み書き許可が実際には与えられないので、このような作業が必要になっています。
    3. 初期化ボタンがクリックされたらアセットにデータベースファイルが存在するか調べて、存在する場合はストレージ領域にファイルをコピーした後に解凍します。アセットにデータベースファイルが存在しない場合はWebコンポーネントを使ってデータベースファイルをダウンロードします。
    4. ダウンロードが成功したら、ストレージ領域にファイルをコピーした後に解凍します。
    5. 解凍が成功したらSQLiteにインポートします。
    6. リセットボタンがクリックされたら直接都道府県リスト手続きを呼び出し、最初から郵便番号検索をやり直します。

     

     

    郵便番号検索

    1. 検索はスピナーで”項目が選択されたら”イベントで処理します。グローバル変数columnに入っているのがprefsならばSQLiteデータベースをopenして”select distinct(city) from japanzip where prefs=都道府県名”と言うSQL文を実行して選択された都道府県内の重複しない市町村名を取得し、その結果からスピナーを作っています。
    2. グローバル変数columnに入っているのがcityならばSQLiteデータベースをopenして”select distinct(street) from japanzip where city=市町村名 and prefs=都道府県名”と言うSQL文を実行して選択された都道府県内の選択された市町村内の重複しない町域名を取得し、その結果からスピナーを作っています。
    3. それ以外ならば選択された都道府県内の選択された市町村内の選択された町域の郵便番号を”select zip from japanzip where street=町域名 and city=市町村名 and prefs=都道府県名”と言うSQL文を実行して取得し、郵便番号のフォーマットを調整後に表示します。

    スマホでテストしてください!

    チュートリアル

    お絵かきアプリを作りましょう

    [プロジェクト]メニューから[プロジェクトを新規作成]を選択し、”Doodle”と名前を付けます。

    デザイン編集

    キャンバスを追加

    描画とアニメーションパレットから、 キャンバスコンポーネントをScreen1にドラッグアンドドロップします。

    キャンバスの高さと幅を”画面いっぱいに表示”に変更します

    キャンバス1のプロパティの高さと幅を”画面いっぱいに表示”に変更します。

    ブロック編集機能を使用したプログラミング

    アプリの動作をプログラミングするには、 ブロック編集機能にアクセスする必要があります。 画面右上のブロック編集ボタンをクリックしてブロック編集機能に行きます。

    キャンバス.ドラッグイベントの追加

    ブロック編集機能の左側にあるキャンバス1パネルをクリックして開きます。 “いつもキャンバス1.ドラッグされたら”ブロックを作業領域(右側の空き領域)にドラッグアンドドロップします。

    キャンバス.線を描画の追加

    キャンバス1パネルにある“キャンバス1.線を描画”ブロックを“キャンバス1.ドラッグされたら”ブロックの中にドラッグアンドドロップします。

    “キャンバス1.線を描画”を完成します

    キャンバス.ドラッグイベントはユーザーが画面上で指を動かしている間に何度でも繰り返し発生するので、前のX、前のYから現在のX、現在のYに線を描くようにします。
     
    これで完成です。スマホでテストしてください。好きな絵を描いてみましょう。
     

    ソースコードのダウンロード

    App Inventorでこのサンプルを使用したい場合は、 ソースコードをコンピュータにダウンロードしてからApp Inventorを開き、[ プロジェクト ]をクリックして[ローカルコンピュータからプロジェクト(.aia)をインポート]を選択し、ソースコードを選択してインポートしてください。