toDataURL()メソッド利用の際の 「Tainted canvases may not be exported」エラーについて

1

Google Cloud のStorageからJavaScriptで画像をダウンロードしてCanvasに書き込んだとき,そのCanvas要素の画像をtoDataURL()メソッドでdataURIに変換する際に

Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

というエラーが出る.直訳すると

「HTMLCanvasElement」で「toDataURL」の実行に失敗しました:汚染されたキャンバスはエクスポートされない可能性があります。

つまり,Corsポリシーに引っかかってしまう.解決するためにはフロントエンドのJavaScriptで任意のドメインからのデータを利用できるように明示的に指定する必要と,生成元からデータを返す際にCorsポリシーを適用しないドメインを明示的に指定する必要がある.

フロントエンドの方法

Canvas要素に描く際に生成元をanounymousに設定すればいい

  download(){
    var gsReference = this.firestorage.refFromURL('gs://my-app.appspot.com/imgs/'+this.authUid +'/'+this.query_img_name)
    const task = gsReference.getDownloadURL().subscribe(dataurl => {
      console.log(dataurl,'tsk');
      const image = new Image();
      image.onload = () => {
        this.canvas.nativeElement.width = image.width;
        this.canvas.nativeElement.height = image.height;
        this.canvas_rendering_context.drawImage(image, 0, 0);
      }
      (→これを追加)image.crossOrigin = "anonymous";
      image.src = dataurl;

    })
  }

生成元の変更

今回はGoogle CloudのStorageから画像をダウンロードしている.従ってStorageの設定をいじる必要がある.

my-cors.jsonなどのファイルを使って設定情報を書いたあと,gsutilコマンドを使ってStorageに反映させればいい.

touch my-cors.json
 nano my-cors.json

my-cors.jsonは下記のように変更しておく

[
    {
      "origin": ["*"],
      "method": ["GET","POST"],
      "responseHeader": ["Content-Type"],
      "maxAgeSeconds": 3600
    }
]

あとはコマンドから反映させる

gsutil cors set ./my-cors.json gs://my-app.appspot.com 

参考文献

https://cloud.google.com/storage/docs/configuring-cors

Angularでpipeを使って日付データをフォーマットする

1

準備

標準機能なので追加のインストールは必要ありません.module.tsで@angular/commonからDatePipeをインストールし,providersに登録します.

import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab3Page } from './tab3.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+import { DatePipe } from '@angular/common';

import { Tab3PageRoutingModule } from './tab3-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    RouterModule.forChild([{ path: '', component: Tab3Page }]),
    Tab3PageRoutingModule,
  ],
-  declarations: [Tab3Page]
+  declarations: [Tab3Page],
+  providers: [DatePipe]
})
export class Tab3PageModule {}

次に,page.tsファイルで読み込みます.

import { Component,ElementRef,ViewChild } from '@angular/core';
+import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {

  constructor(
+    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
  }

}

HTMLでの表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
+  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
+    this.time_on_html = new Date();
+    console.log(this.time_on_html);
  }

}

そして,HTML側は{{time_on_html | date:”MM/dd/yy” }}で参照します.

tsファイルの中で表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
    this.time_on_html = this.datePipe.transform(new Date(), 'yyyyMMddHHmmss');
    console.log(this.time_on_html);

  }

}

2021/07/19 18:42:52と表示される.

Python embeddedのエラー対処法

Python embeddedでmatplotlibを使おうとするとimport errorが起きるエラーです.

import matplotlib

これでエラーが起こる.

ImportError: DLL load failed: The specified module could not be found.

原因

matplotlibを動作するにはMicrosoft Visual C++ のダウンロード が必要です.下記からダウンロードする必要があります.

https://support.microsoft.com/ja-jp/topic/%E6%9C%80%E6%96%B0%E3%81%AE%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%95%E3%82%8C%E3%82%8B-visual-c-%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89-2647da03-1eea-4433-9aff-95f26a218cc0

参考文献

https://www.python.org/downloads/windows/

Angular(Ionic)で画像にお絵描き

1

Angular(Ionic)で画像にお絵描きする最も簡単な方法はプラグインを使うことです.

https://www.npmjs.com/package/ngx-image-drawing

インストール

npm install --save ngx-image-drawing

使い方

モジュールに追加

import { ImageDrawingModule } from 'ngx-image-drawing';

@NgModule({
  imports: [
    ImageDrawingModule
  ],
  declarations: []
})
export class Tab1PageModule {}

あとはHMTLから呼び出せる

<image-drawing
  <div class="upload">
      <input type="file" accept="image/*"
             (change)="fileChangeEvent($event)">
      <img [src]="imgSrc" alt="">
  </div>

  <image-drawing
      [src]="imageUrl"
      outputMimeType="'image/jpeg'"
      outputQuality="0.8"
      (save)="save($event)"
      (cancel)="cancel()">
  </image-drawing>

tsファイルはこれ


    fileChangeEvent(event: any): void {

    //fileが選択されていなければリセット
    if (event.target.files.length === 0) {
      this.file = null;
      this.imgSrc = "";
      return;
    }

    //ファイルの情報をfileとimgSrcに保存
    let reader = new FileReader();
    this.file = event.target.files[0];
    reader.onload = () => {
      this.imgSrc = reader.result;
      this.imageUrl = reader.result;
      //this.cx.drawImage(event.target.files[0], 0, 0, this.width, this.height);

      console.log(this.imgSrc);

    }
    reader.readAsDataURL(this.file);

  }


  save(event: any){
    console.log(event);
    const url = window.URL.createObjectURL(event);
    let title = "Angular_sample_file";
    //this.cx.drawImage(event, 0, 0, this.width, this.height);

    // aタグを作成して無理やりクリック -> ダウンロード機能発火
    let a = document.createElement('a');
    document.body.appendChild(a);
    a.setAttribute('style', 'display: none');
    a.href = url;
    a.download = title;
    a.click();
    window.URL.revokeObjectURL(url);
  }

プラグインを使わない方法

https://medium.com/@tarik.nzl/creating-a-canvas-component-with-free-hand-drawing-with-rxjs-and-angular-61279f577415

pandasの表示列数が少ないので増やす

1

terminalで作業しているとpandasの表示列数が少ない時がある。main.py

(Pdb) pd.DataFrame(data['positions'])
  instrument                                               long  ... unrealizedPL marginUsed
0    USD_CNH  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     -39.1847  2085.3600
1    USD_CHF  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     131.7863  2085.3600
2    NZD_USD  {'units': '400', 'averagePrice': '0.70474', 'p...  ...     175.8605  1184.0320
3    USD_CAD  {'units': '0', 'pl': '0.0000', 'resettablePL':...  ...     118.2543  1668.2880
4    GBP_USD  {'units': '400', 'averagePrice': '1.33051', 'p...  ...     -62.2680  2772.4000
5    AUD_USD  {'units': '400', 'averagePrice': '0.74940', 'p...  ...     169.8251  1257.0400
6    USD_JPY  {'units': '200', 'averagePrice': '104.144', 'p...  ...      21.0000   834.1440
7    EUR_USD  {'units': '1000', 'averagePrice': '1.21002', '...  ...     382.7307  5063.5600

もっと列数をみたい場合、main.py

(Pdb) import pandas as pd
(Pdb) pd.set_option('display.max_rows', 500)
(Pdb) pd.set_option('display.max_columns', 500)
(Pdb) pd.set_option('display.width', 1000)

と打つと、main.py

(Pdb) pd.DataFrame(data['positions'])
  instrument                                               long                                              short        pl resettablePL financing commission dividendAdjustment guaranteedExecutionFees unrealizedPL marginUsed
0    USD_CNH  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '6.53311', '...    0.0000       0.0000   -1.1446     0.0000             0.0000                  0.0000     -39.1847  2085.3600
1    USD_CHF  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '0.88884', '...    0.0000       0.0000   -2.2664     0.0000             0.0000                  0.0000     131.7863  2085.3600
2    NZD_USD  {'units': '400', 'averagePrice': '0.70474', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -0.9832     0.0000             0.0000                  0.0000     175.8605  1184.0320
3    USD_CAD  {'units': '0', 'pl': '0.0000', 'resettablePL':...  {'units': '-400', 'averagePrice': '1.27810', '...    0.0000       0.0000   -4.0178     0.0000             0.0000                  0.0000     118.2543  1668.2880
4    GBP_USD  {'units': '400', 'averagePrice': '1.33051', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -1.7342     0.0000             0.0000                  0.0000     -62.2680  2772.4000
5    AUD_USD  {'units': '400', 'averagePrice': '0.74940', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...    0.0000       0.0000   -1.1558     0.0000             0.0000                  0.0000     169.8251  1257.0400
6    USD_JPY  {'units': '200', 'averagePrice': '104.144', 'p...  {'units': '0', 'pl': '0.0000', 'resettablePL':...  130.6000     130.6000   -3.1302     0.0000             0.0000                  0.0000      21.0000   834.1440
7    EUR_USD  {'units': '1000', 'averagePrice': '1.21002', '...  {'units': '0', 'pl': '20.1626', 'resettablePL'...   20.1626      20.1626  -16.6970     0.0000             0.0000                  0.0000     382.7307  5063.5600

と表示されるようになります。

参考文献

How do I expand the output display to see more columns of a pandas Dat…https://stackoverflow.com

Ionicでカスタムパイプを作成する

1

あるページ内で利用するカスタムパイプを作成する.ngForで繰り返した要素に対して文字列をサニタイズする方法について.一つの要素だけであれば下記方法に従えばできるが,ngForで繰り返した要素に対してサニタイズする方法について.

ionic g pipe app-summarization/customPipe1

そうすると,そのページのモジュールページに下記のコードが追加される.もしされない場合は手動で追加する.

import { CustomPipe1Pipe } from ‘./custom-pipe1.pipe’;

@NgModule({
imports: [
**],
declarations: [**,CustomPipe1Pipe]
})

今回はcustom-pipe1.pipe.tsでサニタイズするので

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'sanitizeHTML'
})
export class CustomPipe1Pipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(code) {
    return this.sanitizer.bypassSecurityTrustHtml(code);
  }
}

使う場所はこんな感じ

    <div *ngFor="let message of messages;">
      <div [ngSwitch]="message['isAi']">
         <div *ngSwitchCase="'true'">
           <div class="media" >
             <div class="media-left">
               <a href="#" class="icon-rounded">AI</a>
             </div>
             <div class="media-body">
               <h4 class="media-heading">Sunny Date:2021/04/17</h4>
               <div [innerHTML]="message.message">{{message.message| sanitizeHTML}}</div>
             </div>
           </div>
         </div>
      </div>
    </div>

Ionicで特定の要素まで自動でスクロールする

1

tsファイルで

import {ViewChild} from ‘@angular/core’
import { IonContent } from ‘@ionic/angulaIonicで特定の要素まで自動でスクロールするr’;

を読み込んだあと,

@ViewChild(IonContent, { static: false }) content: IonContent;

でIoncontentを参照する(@ViewChild(‘ion-content’) content: IonContent;とかくと動かない)

あとは

ionViewDidEnter(){
  this.content.scrollToPoint(0, 600, 300)
}

という感じで指定する.x,y,秒.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {ViewChild} from '@angular/core'
import { IonContent } from '@ionic/angular';

@Component({
  selector: 'app-tab3',
  templateUrl: './tab3.page.html',
  styleUrls: ['./tab3.page.scss'],
})
export class Tab3Page implements OnInit {
  //@ViewChild('ion-content') content: IonContent;
  @ViewChild(IonContent, { static: false }) content: IonContent;


    ionViewDidEnter(){
      this.content.scrollToPoint(0, 600, 300)
    }

GAEでPythonをデプロイする

1

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
import inspect
import pandas as pd


app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    df = pd.DataFrame({'A':[i for i in range(100)]})
    a = str(list(df.T.to_dict().values()))
    print(a)
    return a
if __name__ == "__main__":
    pass
    #app.run(host='127.0.0.1',debug=True)

Flask==1.1.2
Flask-Cors==3.0.10
numpy==1.18.0
pandas==1.2.0
runtime: python37 # or another supported version
service: default
instance_class: B1
basic_scaling:
  max_instances: 1
  idle_timeout: 5m

2021年はPython37が良さそう.requirements.txtはpip3 listで表示されたものを書く.

Angular(Ionic)でHTTPリクエスト

1

AngularでバックエンドAPIへアクセスしてデータを取得する際、認証情報やクエリパラメータを付与したい時がある。

Post通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const body = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com', body, { headers }).subscribe(data => {
        console.log(data);
    });
    }
}

Get通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const parameters = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com',  { headers, parameters }).subscribe(data => {
        console.log(data);
    });
    }
}

Integrate adsense in Ionic Angular.

1

Note:please keep in mind that I am not native English speaker.

In this article, I would like to summarize how to insert ads into Ionic (Angular) project using adsense.

Admob is more popular than adsense for Ionic, but Admob does not support web platforms. If you want to deploy your project as a web app, you need to use Adsense instead of Admob.

However, there is little information on how to use Adsense with Angular, let alone Ionic. Therefore, I would like to introduce the steps here in this article.

Summary

Basically, I recommend you to use ng2-adsense plugin, but it doesn’t work even if you implement it according to the instructions on the official page. You need to add some other code. In addition, it is important to have a ad with fixed size(not responsive). If you set your ad style with responsive, it will not work. Lastly, I recommend you to place your ad on the top area for your page. It may sounds nonsense, but it was important to me.

GitHub page

https://github.com/NP-Systems/Ionic-adsense-demo

Demo site

https://adsense-demo.np-sys.com

Environment

Angular11, Ionic6

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

the version of ng2-adsense was 9.1.0.

Steps

Create your project

ionic start adsense-demo blank --type=angular
cd  adsense-demo

Install plugin from here . https://github.com/scttcper/ng2-adsense

npm install ng2-adsense

Please note that you need to install the one that matches your version of Angular. You can specify plugin version as followings.

npm install ng2-adsense@9.1.0

Next, add the following snippet in Index.html(above </head>).

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

It should look like the picture below.

Next, add following codes to the module where you want to place your ad. Officially, it says to add to app.module.ts, but please add it into the module you want to use. For example, add code into home.module.ts as shown below.

The codes you need to add are followings.

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

You don’t have to add anything to home.page.ts.

Then add snippet below into home.page.html.

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

Please note the followings.

  • Create ad styles in fixed size, not responsive mode (I recommend you to create ad with inline-block; width: 728px; height: 90px).
  • Add the snippet on top of the ion-content tag.

If you set ad style to responsive, it will not work. If you do not add it to the top of the ion-content tag, it will not work neither. I know it sounds strange, but I think Adsense doesn’t recognize content in Angular page. So I recommend you to do that.

Furthermore, it sounds strange to add the snippet starting with “<script” tag. However, this was also necessary for me to work.

You can check adClient and adSlot and snippet in your adsense page.

To see your snippet, click the <> button at the bottom right of the screen below.

Also, to change the style of your ad from responsive to fixed size, press the pencil mark to fix it from responsive.

It it my pleasure to visit following demo site.

https://adsense-demo.np-sys.com

Thank you.

HTMLで要素の位置を調整する

1

要素を真ん中におく

このように真ん中におきたい時

    <div class="ion-text-center my-class-parent">
        <ol class="my-class-child">
          <li>hi</li>
          <li>hi2</li>
        </ol>
    </div>
.my-class-parent {
  height: 100%;
  position: relative;
}

.my-class-child {
  position: absolute;
  left: 50%;
  top:30%;
  padding-left: 0;
}

my-class-parentでBody要素に対する長さを100%に指定しており、それに対してmy-class-childで場所を指定している、

2つの要素を横に並べたい時

並べたい要素をまとめて囲ったTagにdisplay:flexをつけてやる.あとはboxの幅を100%で指定しているので,Imgで幅を指定したらそれを埋めてくれるようになる

                      <div class="flex">
                      <img src="assets/img/syabani.jpg" alt="" >
                      <div class="box">{{welcomeMessage}}</div>
                      </div>

  .flex{
    display: flex;
    /*justify-content: center;*/
  }

  .flex img{
    max-width: 100%;
    width: 30%;
    height: auto;
  }

  div.box{
    width: 100%;
    background-color:#CCCCFF;
}

(Google Cloud Platform)CloudFunctionsとAppEngineどちらにするか

外貨預金としてFXを使用していますが、自動売買のようなものをやってみたくなりました。でもFXは外貨預金として始めたものだし、FXのギャンブル性に危険が怖くもあるので、外貨預金用の口座と自動売買用の口座を別に持つことにしました。

外貨預金用はSBIを使っています(アフィリエイトはやってませんが)

https://www.sbifxt.co.jp/

で、お小遣いを入れるのはOanda。こちらは3年前の口座を復活させました。

https://www.oanda.jp/

余談ですが、SBIFXは大変使いやすいです。外貨預金用としては非常にいいです。ただ、APIを提供していないです。というか、APIを提供しているのはOandaだけなんですよね。。カスタマーサポートは悪いし、スワップポイント低いしで以前愛想つかしたのですが、まぁAPIを提供しているのはここだけなのでしょうがないですね。

SBIFXは預金用です。売買は基本しません。超長期保有です。Oandaはお小遣い、遊びという位置付けです。自由にやります。昨日、名古屋の丸善でこんな本を買いました。おもしろかったです。これを実現するようなソフトを作成することにしました。

どうやってソフトを作ろうかという話で、最初はRasberryPi4にしようとおもったんですよね。メモリも4Gbあるので、これをローカルで動かして、為替データを取得して、グラフ化するというのはどうかというわけです。これは2018年に仮想通貨で使った手法なので簡単にできるのですが、なんか以前と同じ方法というのはつまらないと思いました。

そこで、今回はGoogleCloudPlatformとWebを使って同じことができないかと考えました。

GoogleCloudPlatformで行うことは2つです。

1。為替APIを使って定常的にデータベースへ保存する

2。保存したデータを取得するAPIをさらに自作する

そのうえで、

3。自作APIでデータを扱えるWebアプリを作成します。

で、GoogleCloudPlatformで上記を実現するのにGoogleAppEngineとGoogleCloudFunctionsのどちらがいいか迷いました。先日作成したアプリではGoogleCloudFunctionsを使っているのですが、GoogleAppEngineのほうがメジャーですし、別にGoogleCloudFunctionsで不満はないのですが使いやすいのかなと。。でちょっと調べてみたんですけど、そもそも値段が違うんですよね。GoogleCloudFunctionsは一つの関数しか割り当てられない代わりにものすごくやすい。200万回の呼び出しは無料で、次の200万回で1ドル程度の料金です。驚きの安さである一方、GoogleAppEngineは驚きの高さなんですよね。。一番安いプランでも月に7ドルかかってしまいます。一つの関数しか割り当てられないGoogleCloudFunctionsと比べて使い勝手はよさそうだけど、あまり使いこなせるのかと。。

https://cloud.google.com/appengine/pricing?hl=ja

AppEngineは時間に対して料金がかかります。アイオワのサーバでインスタンスを作成した場合、1時間あたり0.01ドルなので、月に7ドル程度はかかってしまいます。

CloudFunctionsの場合

https://cloud.google.com/functions/pricing

こちらの場合は、回数に対して費用が発生します。最初の200万回が無料ということなので、よっぽどアクセスを集められる以外はCloudFunctionsのほうがよさそうです。

ただ、CloudFunctionsの場合は一つの関数しかアップできないのであまり使い勝手はよくないかもしれません。まぁ、CloudFunctionsで初めてみてからの移行にしようかな。。

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

1

Formの場合はこちら

https://ionicframework.com/docs/v3/developer-resources/forms/

Note: If you use ngModel within a Form tag, you have to provide a name property. If you do not, you must set standalone to true in ngModelOptions

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Angularでfirestoreのコレクションからデータ一覧を取得する

1

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

HuaweiのHMSとはなにか

1

HMSとはなにか

HMSとは、Huawei Mobile Serviceの略です。Google Mobile Service(GMS)のHuaweiバージョンです。

GMSとは何かがわかればHMSがどういうものかすぐにわかると思いますが、これは素のAndroidにGmail,YoutubeなどGoogle独自のアプリや機能を付与したものになります。

2019年からの貿易戦争により、アメリカは中国に対して禁輸措置をとりました。その結果、HuaweiはGMSに代わりサービスを独自で提供する必要に迫られました。具体的には、先ほど挙げたメールサービスやマップ、ユーザー認証などになります。詳細を見ていきましょう。

HMSの2つの構成

HMSには、利用者用のコンポーネント(AppGallery、その他のファーストパーティのHuaweiアプリとサービス)と、HMS Coreと呼ばれる開発者向けコンポーネントがあります。HMS Coreは、開発者がアプリの作成と改善に使用できるさまざまなAPI、SDK、およびサービスで構成されています。

ユーザー向けHMS

Huawei ID

GoogleアカウントやAppleIDに相当します。ユーザーのデータをこれに紐づけて管理します。

Huawei Mobile Cloud

HuaweiIDごとに用意されたクラウドのストレージです。最大5GBまで写真や音楽などが保存できます。

Huawei Assistant

これはまぁSiriのようなものなのかと。

Huawei Themes

スマホのテーマを設定できます。

Huawei Music

音楽を管理します。

Huawei Video

動画を管理します。

Huawei Browser

専用のブラウザです。

Huawei AppGallery

Google Playに相当するものですね。

HMSコア

HMSコアには下記が含まれます。結構いろいろあります。

詳細は公式でどうぞ

https://developer.huawei.com/consumer/en/hms

IonicのAndroidアプリをWebアプリケーションとしてデプロイする

1

概要

Angular,IonicをWebアプリとしてデプロイする方法について記述する。

firebaseを使ってホスティングする方法と、Xserverでapacheでホスティングする方法を試した。firebaseでホスティングするのは(慣れてしまえば)至れり尽くせりであるが、先のことを考えると値段が気になるためXserverのほうが望ましいように感じた。

Xserverで既にドメインを持っていれば、そのサブドメインにデプロイすることで追加負担0でサイトをデプロイすることができる。速度も速いし、こちらのほうがいい。

Firebaseでのホスティング

https://qiita.com/M_Faust/items/a514b8a79d2fd13cb72b

https://qiita.com/daikiojm/items/89f46bd83c9a2285bbc6

が参考になった。

まず、ライブラリのインストールをする

npm install -g firebase-tools

ログインする

firebase login

なお、.firebasercというファイルが生成されているので2度目以降はこれを消す。macならcommand + shift + . で隠しファイルを表示できる

初期設定を行う

firebase init

なにをしたいか聞かれるので
Hosting: Configure and deploy Firebase Hosting sitesをスペースで選択する。

What do you want to use as your public directory?

と聞かれたら、アップロードしたいファイル一式があるフォルダを指定する。私の場合はIonicだったのでwwwだった。

Configure as a single-page app (rewrite all urls to /index.html)? Yes

と言う質問については、Ionicなのでyest

File www/index.html already exists. Overwrite? (y/N)

これは、先のフォルダで指定した場所のindex.htmlをこの場でテスト用のものに上書きしますかと言うことなので、Noをしておく

その後、

firebase deploy

とすればいい。

Xserverの場合

流れは、xserverのファイルマネージャで確認できる場所へファイルをFTPソフトを使って自分でアップロードし、apacheの設定を.httpaccessとhtconfで行う

https://www.xserver.ne.jp/login_file.php

私はfirezillaを使った。

ionic build --prod

で生成されるファイル郡をfilezillaでアップロードする。私の場合は、サブドメインだったので、サブドメイン用のフォルダ(pwaと言うもの)にアップロードした

その後、pwaの中に.httpaccessファイルを生成し、さらにルートフォルダ(public_html)にhttpd.confを作成し、編集した

https://mi12cp.hatenablog.com/entry/2018/08/25/224149
<Directory "/pwa">
#   Options Indexes FollowSymLinks
    Options FollowSymLinks

#   AllowOverride None
    AllowOverride All

    Require all granted
</Directory>

.htaccessは下記

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

Angular(Ionic)でStripeでクレカ決済を導入する

目的

Angular(Ionic)でStripeでクレカ決済を導入するにあたり、ソフト側の設定方法をまとめる。AndroidのPlay Storeでの設定方法は下記参照。

概要、考え方

・クライアント側とサーバ側で処理が必要になる。

Stripeのダッシュボードにいくと、公開キーと秘密キーが得られる。

支払いを完了するさい、まずはサーバ側で支払い情報を作っておく。curlでとりあえずはいける。

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_秘密キー \
  -d amount=100 \
  -d currency=jpy

そうすると、返り値に下記が得られる。

ここにあるClient_Secretと言うものがあるのでこれを使って、ConfirmPaymentメソッドで支払いをクライアントを行う。

https://stripe.com/docs/js/payment_intents/confirm_card_payment

Angular(Ionic)でAdsenseを導入する

1

AngularでAdsenseで広告を出す方法です。

AndroidやiOSアプリで広告を出すためにはAdmobが利用可能ですが、AdmobはWebプラットフフォームをサポートしておりませんのでWebでは使えません。Webの場合はAdsenseを使う必要があります。

ところがAngularでAdsenseを利用する方法はほとんど情報がなく、ましてやIonicとなると皆無でした。非常に苦労しましたが、なんとかわかってきたのでまとめます。

まとめ

ng2-adsenseを使います。しかし公式の通りにやっても動作しませんので、追加でHTMLページにコードを追加する必要があります。また、広告をレスポンシブにしない、ページの上部に設置するといった一見ナンセンスなことに注意する必要があります。

環境

Angular11でIonic6です。

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

ng2-adsenseのバージョンは9.01でした。

手順

https://github.com/scttcper/ng2-adsenseからプラグインをInstallします

npm install ng2-adsense

Angularとバージョンを合わせる必要がありますので、公式のGitHubから自分が利用するAngularにあったバージョンをインストールしてください。バージョンを指定する場合は@の後にバージョンを指定します。

npm install ng2-adsense@9.1.0

次に、Index.htmlの<head></head>に下記のスニペットを追加します。

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

こんな感じになります。Angular、Ionicでscriptタグを利用することはほとんどありませんが、index.htmlなら貼り付けても動作しますので下記のようにしてください。

次に、広告を導入したいモジュールにコードを追加していきます。公式ではapp.module.tsに追加と書いてありますが、使いたいモジュールで追加してください。例えば下記のようなhome.module.tsを編集します。

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

AdClientなどはAdsenseのサイトから確認していただけると思います。

home.page.tsには何も追加しなくていいです。home.page.htmlを次に編集します。home.page.htmlのion-contentタグの一番上に”<script”から始まる部分を追加します。

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

注意点は下記です。

  • 広告のStyleはレスポンシブにせず、固定(inline-block;width:728px;height:90pxで動作確認)にする
  • ng-adsenseタグだけではなく、スクリプトタグなど部分も追加する
  • ion-contentタグの一番上に追加すること

です。これを見つけ出すのに非常に苦労しました。

広告のStyleはレスポンシブにすると表示されなくなります。ion-contentタグの一番上に追加することも似たように少しおかしな内容ですが、おそらくAdsense側でAngularのページは認識していないのだと思います。したがってページの途中に「最適な」形で読み込ませようとしてもそれが叶わないので表示されないのだと思います。それがレスポンシブにしたりページ途中に入れるとダメになることではないかと。

一方、ng-adsenseタグだけではなく、”<script”から始まる部分も追加するというのは意味がわかりません。AngularでHTMLにスクリプトを埋め込んでそれが機能するので意味がわかりませんが、動作させるためには必要でした。なんでサニタイズしなくてもいいのかは全くわかりませんが、動作はしました。

なお、adClientとadSlot、追加用のスクリプトなどはadsenseのホームページから確認可能です。

スクリプトの確認のためには下記画面の右下の<>のボタンをクリックしてください。

また、広告のスタイルを固定するためには、鉛筆のマークを押してレスポンシブから固定にします。

まじで苦労してけど表示されるようになった。

https://ocr-app.np-sys.com/

AngularでSEOを考えたときに考慮すること

背景

AngularでSEOに配慮する場合、何に気をつければいいかGoogle Developers Codelabsで勉強したのでその内容を記載します!

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly

Google Developers CodelabはハンズオンでGoogleのサービスが学べるもの

https://codelabs.developers.google.com/

1.まずはモバイルフレンドリーかチェックせよ

モバイルファーストと言われて久しいですが、まずはモバイルフレンドリーかチェックをするように記載されていました。

便利なサイトが紹介されていて、このサイトでリンク貼り付ければ調べられます

https://search.google.com/test/mobile-friendly

2.ページ毎にタイトルとメタデータを設定せよ

2番目に紹介されていたのは、基本的なことだが、ページにタイトルを付与し、メタデータディスクリプションを設定することだった。確かにAngularで作成する際、設定したことがなかった。勉強になった。AngularのConponentに直接タイトルとメタデータディスクリプションは設定できないとのことなので、BrowserModuleを使って設定するとよさそう。

https://www.atmarkit.co.jp/ait/articles/1802/05/news142.html

https://www.atmarkit.co.jp/ait/articles/1803/30/news135.html

3.Googlebotが見つけられるようにリンクを記載する

google botがURLを見つけられるようにリンクをちゃんと記載する。

Codelabにはこう記載があった。

A few tips on links

As a rule of thumb, to make sure Googlebot finds all views, make sure that:

  • All links are implemented as <a href="">
  • All links have a valid URL as their href attribute (no javascript: URLs)
  • All views have a URL that doesn’t use the fragment identifier to load different content (no URLs like “/#example” or “/#!example“, sometimes called a hash URL)

要するにハッシュタグを使わないこと、aタグを使うようにとのこと、JavaScriptでレンダリングしないようにするとのことだった。

例として出されていたのは下記

<button class="mdc-button mdc-button--outlined">
  <span class="mdc-button__label">Learn more</span>
</button>

はこう書く

<a class="mdc-button mdc-button--outlined">Learn more</a>

対応するJavaScriptはこレから

item.querySelector('button').addEventListener('click', function () {
  location.href = story.link;
});

これに変える

item.querySelector('a').setAttribute('href', story.link);

このサイトでパフォーマンスを調べると良いとのこと。

https://developers.google.com/web/tools/lighthouse/

エラー時における処理

ページが見つからない場合、リダイレクトするようにする

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly/#6

所感

大変勉強になった。AngularだからSEOが不利というわけでもなさそうです。

Angular(Ionic)をWebアプリケーションとしてapache(Xserver)でデプロイする

1

XserverでAngular(Ionic)をホスティングする方法です。

流れとしては

  • サブドメインを設定
  • そのサブドメインにionic build –prodで生成したファイルをアップロードする
  • そのサブドメインのルートに.htaccessを設置する
  • 常時SSL化する
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

https://awesome-rainbow-colors.com/2020/07/25/ionic%E3%81%AEandroid%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4/

常時SSL化する

常時SSL化するためには、2つのことを行います

https://www.xserver.ne.jp/manual/man_server_fullssl.php

まず、無料独自SSLを導入します。これでHTTPSで接続するとSSL通信できるようになります。

さらに、HTTPで接続した時に自動でリダイレクトするように.htaccessに下記を追加します。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

なので最終的には、下記の.htaccessを作成して、サブドメインのプロジェクトのルートフォルダに設置すればいいです。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

GitHubのREADME.mdをグラフ入りで効率的に書く方法

GitHubのREADME.mdをグラフ入りで効率的に書く方法です。

  • VSCodeでMarkdown Preview Enhancedを入れておく
  • 普通にREADME.mdを作成する
  • 文章はここに書く
  • グラフはグラフごとに.mdファイルを生成して、Markdown Preview Enhancedに入っているmermaid.jsを使ってグラフを書く
  • それをJPEGやPNGとしてエクスポートする
  • その画像をREADME.mdから読み込む

これが一番早そうです。

Ionic(Angular)でAdmobを導入する方法(iOS編)

1

Ionic(Angular)でAdmobを導入する方法です。こちらはiOSでの実装となります。Androidでの実装方法はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+Plugin.swiftの編集が必要です(この記事に方法あります)

流れ

  1. npmでcapacitor-admobをインストールし、info.plistを編集します
  2. そのままだとエラーが起こるので、Plugin.swiftを修正します
  3. app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でiOS13から14.3で確認しています。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

作成方法はこちら。


見た目はこんな感じ。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. iOSのプロジェクトを生成する

iOSのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add ios
npx cap sync
npx cap open ios

3. info.plistを編集する

info.plistとは、iOSアプリの設定情報などを記入するものです。インフォプロパティリストと読みます。下記コマンドでXcodeを開きます。

npx cap open ios

info.plistを選択します。

このファイルを編集するのですが、編集するにはコツが入ります、info.plistの上で右クリックをして、open as > source codeを選択して編集できるようにします。

そして、下記のスニペットを下の方に貼り付けます。自分のアプリIDに変更するのを忘れないでください。

    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~XXXXXXXX</string>

もし場所がわからない方がいたらinfo.plistを丸ごと貼り付けるので参考にしてください。下から3行目から8行目までが貼り付けたものです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
        <string>admob-master</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLName</key>
			<string>com.getcapacitor.capacitor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>capacitor</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
	<key>NSCameraUsageDescription</key>
	<string>To Take Photos and Video</string>
	<key>NSLocationAlwaysUsageDescription</key>
	<string>Always allow Geolocation?</string>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>Allow Geolocation?</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>To Record Audio With Video</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>Store camera photos to camera</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>To Pick Photos from Library</string>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<true/>
    
    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~3362455272</string>
    
</dict>
</plist>

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

右上を見ると赤い背景で9というエラーが表示されています。その右下の別の赤いボタンからエラーが起きている「Plugin.swift」へ移動します。

そうするとそれぞれのエラーの詳細が確認できます。

よく見るとどれも同じメッセージのようです。こう書いてあります、

Method 'interstitialDidReceiveAd' must be declared public because it matches a requirement in public protocol 'GADInterstitialDelegate'

publicで宣言せよと書いてあるのでそうします。

修正前

    private func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

修正後

    public func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

合計9箇所同じように変えると、ビルドできるようになるはずです。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync ios
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった!お疲れ様でした。

Ionic(Angular)でAdmobを導入する方法(Android編)

1

Ionic(Angular)でAdmobを導入する方法です。こちらはAndroidでの実装となります。iOS版はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+依存関係の解決(この記事に方法あります)が必要です

流れ

  1. npmでcapacitor-admobをインストールします
  2. そのままだとエラーが起こるので、依存関係を解決します
  3. エミュレータで正しくアプリが立ち上がり依存関係が解決されていることを確認したのち、app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でAndroid SDKはAPIレベル29(Android X)で検証。28以下でもいけました。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. Androidのプロジェクトを生成する

Androidのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add android
npx cap sync
npx cap open android

3. AndroidManifest.xmlを編集する

Androidのマニフェストファイルはアプリに機能を追加するときに編集するものです。Android Studioではデフォルトで作成されるものとなります。

ここからどのファイルを編集するか分かりにくいので詳細に記載します。非常にここは苦労しました。

デフォルトでは下記のようにAndroidが選択されていると思います。

ドリルダウンを選択してProjectを選択します

この状態にまずしてください。

マニフェストファイルはProjectのandroid/app/src/main/AndroidManifest.xmlにあります。

自分のAdmobのアプリIDに編集する必要がありますが、下記のように変更します。

<application>
  <!-- this line needs to be added (replace the value!) -->
         <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXX" />
  <activity></activity>
</application>

つまり、編集した後のファイルは下記のようになります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.ionic.starter">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXXX" />

        <activity
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
            android:name="io.ionic.starter.MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBarLaunch"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/custom_url_scheme" />
            </intent-filter>

        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>

    <!-- Permissions -->

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Camera, Photos, input file -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Geolocation API -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" />
    <!-- Network API -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- Navigator.getUserMedia -->
    <!-- Video -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- Audio -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
</manifest>

4. MainActivity.javaを編集する

admobをandroidに登録します。

Projectを選択した状態で、Android>app>src>main>java><your original name>>MainActivityを選択します。

そして、このように変更します。

// Other imports...
import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{

      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

つまり、このようにします。

package io.ionic.starter;

import android.os.Bundle;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import java.util.ArrayList;

import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

/Users/masaya/Desktop/MyGoodness/admob-master/admob-master/node_modules/capacitor-admob/android/src/main/java/app/xplatform/capacitor/plugins/AdMob.java:4: エラー: パッケージandroid.support.design.widgetは存在しません
import android.support.design.widget.CoordinatorLayout;

そこで、焦らずに下記の対応を行います。

https://github.com/rahadur/capacitor-admob/issues/35

Admob.javaの編集

まず、エラーが出ているAdmob.javaのファイルのエラー発生している行を下記に変更します。

import androidx.coordinatorlayout.widget.CoordinatorLayout;

前のものはコメントアウトでもしておいてこのようにします。

そして、capacito-admobのbuild.gradleのdependenciesの中にimplementation ‘androidx.coordinatorlayout:coordinatorlayout:1.1.0’を追加します。

implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'

編集するファイルを間違えるとドツボにハマるので注意。

これでサイドエミュレータを立ち上げると下記のエラーが出ます。

2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: io.ionic.starter, PID: 9236
    java.lang.RuntimeException: Unable to start activity ComponentInfo{io.ionic.starter/io.ionic.starter.MainActivity}: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: java.lang.ClassNotFoundException: android.support.design.widget.CoordinatorLayout
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:454)
        at android.view.LayoutInflater.createView(LayoutInflater.java:815)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:659)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:60)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:48)
        at io.ionic.starter.MainActivity.onCreate(MainActivity.java:17)
        at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.design.widget.CoordinatorLayout" on path: DexPathList[[zip file "/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/base.apk"],nativeLibraryDirectories=[/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/lib/x86, /system/lib, /system/product/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        	... 28 more

そこで、この辺を解決してくれるように下記のライブラリを導入します。

terminalで下記を打ちます。

npm install jetifier
npx jetify
npx cap sync android

これで再度エミュレータを起動すると、ようやく無事に立ち上がるようになります。これで依存関係が無事に解決されました。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync android
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった。お疲れ様でした。

Angularで画像をトリミングする方法

1

Angularで読み込んだ画像をトリミングできるようにする方法です。

ポイント

  • ngx-image-cropperを使います

流れ

  1. ngx-image-cropperをnpmでinstallします
  2. 読み込みたい場所でngx-image-cropperをmoduleに登録します
  3. その場所のHTMLとTSファイルを編集します

環境

Ionic 4, 5, 6で動作検証しています。

ソースコード

GitHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1137-imageCropper

参考文献

https://www.npmjs.com/package/ngx-image-cropper

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装方法

それではTab1ページに実装していきます。

インストール

プロジェクトのフォルダに移動して下記コマンドでインストールします。

npm install ngx-image-cropper --save

モジュールへの登録

今回はtab1に実装することにするので、tab1.module.tsに下記のようにします。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ImageCropperModule } from 'ngx-image-cropper';

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule,
    ImageCropperModule
  ],
  declarations: [Tab1Page]
})
export class Tab1PageModule {}

import { ImageCropperModule } from ‘ngx-image-cropper’;を追加して、

@NgModuleのimportsにImageCropperModuleを追加します。

HTMLとTSファイルの編集

上記で準備は完了なので、HTMLとTSファイルの編集を行います。

tab1.page.htmlに下記のように追加します、

<input type="file" (change)="fileChangeEvent($event)" />

<image-cropper
    [imageChangedEvent]="imageChangedEvent"
    [maintainAspectRatio]="true"
    [aspectRatio]="4 / 3"
    format="png"
    (imageCropped)="imageCropped($event)"
    (imageLoaded)="imageLoaded()"
    (cropperReady)="cropperReady()"
    (loadImageFailed)="loadImageFailed()"
></image-cropper>

<img [src]="croppedImage" />

tab1.page.tsを下記のようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
    imageChangedEvent: any = '';
    croppedImage: any = '';

    constructor(
    ) {}

    fileChangeEvent(event: any): void {
        this.imageChangedEvent = event;
    }
    imageCropped(event: ImageCroppedEvent) {
        this.croppedImage = event.base64;
    }
    imageLoaded(image: HTMLImageElement) {
        // show cropper
    }
    cropperReady() {
        // cropper ready
    }
    loadImageFailed() {
        // show message
    }

}

応用編

以上が基礎ですが、ここからは切り取った画像を新たに別の画像をとして読み込む方法について記載します。

まず、HTMLに切り取った後に押下するためのボタンを配置します。

tab1.page.htmlの当該部分を下記のように変更します。

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-item>
    <input type="file" accept='image/*'  (change)="fileChangeEvent($event)" />
  </ion-item>

  <ion-progress-bar type="indeterminate" *ngIf="show" reversed="true"></ion-progress-bar>
  <!--プログレスバーの追加です-->

  <image-cropper
  [imageChangedEvent]="imageChangedEvent"
  [maintainAspectRatio]="false"
  [aspectRatio]="5/ 3"
  format="jpeg"
  (imageCropped)="imageCropped($event)"
  (imageLoaded)="imageLoaded()"
  (cropperReady)="cropperReady()"
  (loadImageFailed)="loadImageFailed()"
  imageQuality=25
></image-cropper>

  <img [src]="croppedImage" />

  <ion-button expand='block' (click)="onButtonClicked()" shape='round'>Get cropped image</ion-button>

</ion-content>

そして、tsファイルで追加で下記2つのモジュールを読み込みます。

import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

'
'
  constructor(
    public loadingController: LoadingController,
  ) {}

そして、tsファイルの変数として下記を追加します。

export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  croppedFileObject:any;
  show:any=false;
  • imageChangedEventは、画像の選択範囲が変わったら都度呼び出されるイベントに関する情報を保持するものです。
  • originalImgNameは、inputタグで読み込んだ画像から元のファイル名を取得できるのでそれを格納する変数です
  • buffCroppedImageBase64は、切り取った画像(base64です)を格納する一次ファイルです。HTML側で<img [src]=”croppedImageBase64″ />と定義していますが、ユーザーが選んでいる時に都度croppedImageBase64″に入れると切り取った画像がチラチラしてしまうので、一時的に格納するものとしてbuffCroppedImageBase64を用意しました。
  • CroppedImageBase64は最終的な結果を入れるものです。ここにBase64の画像を入れると、HTML側で表示されます
  • croppedFileObjectは、切り取った画像を別のファイルとして読み込むのに使用します。
  • showはProgressバーを制御するものです

fileChangeEventの部分を下記のように拡張します。

  fileChangeEvent(event: any): void {
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }

重い画像だと読み込むのに数秒かかってしまいます。そこで、読み込み開始したらprogressバーで動作していることを知らせています、500は使った感触で決めました。

imageCroppedの関数を下記のように変えます。

  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }

先ほど行ったように一度Bafferの方に切り取った画像を入れています。

最後にonButtonClickedの関数を下記のように変更します。


  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }

最終的に下記にようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';
import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  show:any=false;

  constructor(
    public loadingController: LoadingController,
  ) {}

  fileChangeEvent(event: any): void {
    console.log(event,'abcde');
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }


  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }
  imageLoaded(image: HTMLImageElement) {
      // show cropper
  }
  cropperReady() {
      // cropper ready
  }
  loadImageFailed() {
      // show message
  }

  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }


}

Ionic(Angular)でアコーディオンを実装する方法

1

Ionic(Angular)でアコーディオンを実装する方法です。

ポイント

  • Componentを自作して、それを読み込むようにします
  • これによりいろいろな場所で使い回しができるようになります

結果

こんな感じでクリックするとカードが広がるようになります。

環境

Ionic 4, 5, 6で動作検証しています。

流れ

  1. コンポーネントを作成し、HTMLとSCSSを記載します
  2. 読み込みたい場所で作成したコンポーネントをmoduleに登録します
  3. 使いたい場所のHTMLでapp-expandableタグで呼び出します

ソースコード

GItHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1131-createAccordion/frontend

参考文献

https://www.joshmorony.com/creating-an-accordion-list-in-ionic/

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

自作コンポーネントの作成

下記のコマンドで自作コンポーネントを作成します。

% ionic g component components/Expandable

app.module.tsと同じ階層にcomponentsフォルダができて新しいコンポーネントが生成されます。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── components
│   └── expandable
│       ├── expandable.component.html
│       ├── expandable.component.scss
│       ├── expandable.component.spec.ts
│       └── expandable.component.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

tab1.module.tsでの読み込み

今回はtab1ページに埋め込むことにするので、tab1.module.tsに下記のように登録します。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ExpandableComponent } from "../components/expandable/expandable.component";

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule
  ],
  declarations: [Tab1Page,ExpandableComponent]
})
export class Tab1PageModule {}

expandable.component.htmlの変更

 src/components/expandable/expandable.component.htmlのファイルを下記のように変更します。

<p>
  expandable works!
</p>

<div #expandWrapper class='expand-wrapper' [class.collapsed]="!expanded">
    <ng-content></ng-content>
</div>

expandable.component.scssの変更

.expand-wrapper {
  transition: max-height 0.4s ease-in-out;
  overflow: hidden;
  height: auto;
}

.collapsed {
  max-height: 0 !important;
}

expandable.component.tsの変更

Before

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-expandable',
  templateUrl: './expandable.component.html',
  styleUrls: ['./expandable.component.scss'],
})
export class ExpandableComponent implements OnInit {

  constructor() { }

  ngOnInit() {}

}

After

import { Component, AfterViewInit, Input, ViewChild,  ElementRef, Renderer2 } from "@angular/core";

@Component({
  selector: "app-expandable",
  templateUrl: "./expandable.component.html",
  styleUrls: ["./expandable.component.scss"]
})
export class ExpandableComponent implements AfterViewInit {
  @ViewChild("expandWrapper", { read: ElementRef }) expandWrapper: ElementRef;
  @Input("expanded") expanded: boolean = false;
  @Input("expandHeight") expandHeight: string = "150px";

  constructor(public renderer: Renderer2) {}

  ngAfterViewInit() {
    this.renderer.setStyle(this.expandWrapper.nativeElement, "max-height", this.expandHeight);
  }
}

tab1.page.htmlの変更

Before

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

After

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card (click)="expandItem(item)" *ngFor="let item of items">
    <ion-card-header>
      <ion-card-title>隣のトトロ</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item.expanded">
        <p>
          とっとろ、と、とーろ
        </p>
        <p>
          とっとろ、と、とーろ
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>
</ion-content>

tab1.page.tsの変更

Before

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {

  constructor() {}

}

After

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  public items: any = [];

  constructor() {
    this.items = [
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false }
    ];
  }

  expandItem(item): void {
    if (item.expanded) {
      item.expanded = false;
    } else {
      this.items.map(listItem => {
        if (item == listItem) {
          listItem.expanded = !listItem.expanded;
        } else {
          listItem.expanded = false;
        }
        return listItem;
      });
    }
  }
}

FileZillaでローカルのファイルにアクセスできない

1

FileZillaで「このディレクトリを表示する権限がありません」という表示が出てローカルのファイルにアクセスできないときは、

左上のリンゴのマークから「システム環境設定」から「セキュリティとプライバシー」を選択し、「ファイルとフォルダ」からFileZillaのアクセスできる場所を設定します。

自社内アプリを開発する私がIonicを気に入っている理由

1

Ionicとはなにか

会社の中で使う業務アプリをよくlonic(Angular)で作っています。ここでは、自社内アプリを開発する私がIonicを気に入っている理由を解説します。

一応Ionicについて説明しておくと、

  • HTMLやJavascript(TypeScript)系のWeb技術を使ってAndroid, iOSのスマホアプリやWindows,Mac向けのデスクトップアプリを作るフレームワーク
  • 「Web Components」というWeb標準の技術が採用されており、もっとも推奨されているのはAngularですが、ReactやVueからも利用可能

結論

先に結論書くとIonicを使う理由は

  • UI構築が楽
  • ページ遷移時の動作が良い
  • バンドルサイズが小さい
  • Capacitorと連携してNativeの機能にアクセスできる

からです。

UI構築が楽

自社内アプリなので、デザインは凝らなくていいんですよね。ださくなければいいです。以前Cordovaでスマホアプリを開発していましたのですが、さすがにHTMLとJavaScriptで記述したWebページがそのままAndroid, iOSで動作するとダサいです。

そ決してCordovaが悪いわけではないのですが、HTMLとJavaScriptで記述したWebページがそのままアプリになる以上、どうしてもダサいです。例えばボタン一つとってもbootstrapを使えばWebでは綺麗に見えるのにアプリで見るとなんか違う感が拭えなかったです。Monacaのonsen.uiというものでNativeの見た目を再現できるということでしたが、個人的な感想としてMonacaは嫌いでした。

  • ビルドがあまりにも遅すぎてPDCAが回せない
  • 有料でも作成できるプロジェクト数が不便を感じるほど限られている
  • Monacaという名称やonsen.uiという名前がときめかない
  • Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる

というデメリットを感じ使用を中止しました。特に最後の「Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる」というのは強烈で、有料プランでエンパワーメントしてもらえると思っていたのに、実際は囲い込まれているということで非常に残念に思いました。

その点、IonicはiOS/Android別の美しいUIを提供してくれます。IonicがAngularやReactから使えることからわかるように、Ionicの本質はUIコンポーネントのライブラリです。あるコンポーネント(例えばボタン)を設置したとしましょう。そうすると、iOSで表示した場合はAppleのHuman Interface Guidelinesに基づいたデザイン、Androidで表示した場合はマテリアルデザインと自動的に表示が切り替わります。これは実際に書いていて最高にウキウキするIonicの素晴らしいところです。

また、こちらの記事(Cordovaをdisる人類全員に読んでほしい「Cordovaつらいを考える」)に記載されているように、60fpsでの表示で滑らかに動くことや、無限スクロールが可能なことも拡張性を保証する大事なポイントだと考えています。

ページ遷移時の動作が良い

最近、スマホ画面の下にTabがあるアプリケーションが多くあります。Cordovaでも当然実装できるのですが、音楽を入れた時にページが切り替わるたび音楽が初めから再生されるという非常に残念な思いをしたことがあります。

その点、IonicはTabを切り替えてもバックグラウンド音楽が途切れない。。Nativeで開発しているわけではないのに、Nativeで違うことで悩まされないというのは地味ですが大きいと考えています。

この辺を非線形ナビゲーション」と「ナビゲーションスタック」というらしいですが、Ionicのドキュメントからコアコンセプトのページがなくなっていたので詳細をしらべることができませんでした。

バンドルサイズが小さい

特にAndroidでは顕著ですが、アプリケーションのバンドルサイズは非常に重要視されています。Androidアプリを開発されている方はご存知かもしれませんが、近年、Androidアプリの拡張子は.apkではないのです。デベロッパーはaboというプレビルドしたような形でアップロードしておいて、ユーザーがインストールする際、そのデバイスに最適化したapkをGoogleが生成してダウンロードします。日本のように通信環境が整っていない国も多いなかで、バンドルサイズを小さくすることがいかに重要視されているかわかると思います。

その点、lonicは、「Stencil」というWeb Components開発ライブラリによって作られており、バンドルサイズはとても小さくなります。

また、LazyLoadingという最初に全てを読み込むわけではなく、必要なものはあとで読み込む機能を実装しているので、軽量に高速で動作します。LazyLoadingはAngularにも実装されているので迷うかもしれませんが、こちらに英語ですがIonic X AngularにおけるLazy Loadingの実装方法について記事がまとまっています。このように公式が充実しているのもIonicのいいところです。https://ionicframework.com/blog/how-to-lazy-load-in-ionic-angular/

なお、Angularについて解説した記事はこちらですので、Angularについて知りたい方はどうぞ。

https://top.np-sys.com/general/%e5%80%8b%e4%ba%ba%e3%83%bb%e5%b0%91%e4%ba%ba%e6%95%b0%e3%81%ae%e3%82%b7%e3%82%b9%e3%83%86%e3%83%a0%e9%96%8b%e7%99%ba%e3%81%ab%e3%81%afangular%e3%81%8c%e3%82%aa%e3%82%b9%e3%82%b9%e3%83%a1/

Nativeの機能にアクセスできる

Ionicは(Cordovaもそうですが)Webページをアプリにできるだけではなく、そこにNativeのカメラ、位置情報などのAPIにアクセスできることは大きな魅力です。Ionicにはさまざまなプラグインがありますので、これを利用することでWeb技術を使いながら、それを超えたアプリケーションを作成することが可能です。

Ionicプラグインの導入方法

総括、その他

 Ionicについて簡単に解説しました。拡張性が担保されたフレームワークでプロジェクトをひとつ作り、それをそれぞれの最高のUIでクロスプラットフォームに出力できる点が一番気に入っています。

AngularでIonicを用いてtab barのデザインをカスタマイズする

1

Angualrとクロスプラットフォームアプリ開発用のフレームワークであるIonicを組み合わせて、Tab barをカスタマイズする方法を解説します。

ポイント

  • Tab barを備えたプロジェクトはIonicが提供しているテンプレートを使用
  • 比較的コードは単純で実装は容易

Github

Githubにコードあげています。

https://github.com/NP-Systems/Ionic_tips/tree/id-1113-customize-tab-bar

実装手順

Ionicが提供しているTab barを備えたテンプレートをダウンロードします。

% ionic start

Pick a framework! ?

Please select the JavaScript framework to use for your new app. To bypass this
prompt next time, supply a value for the --type option.

? Framework: Angular

Every great app needs a name! ?

Please enter the full name of your app. You can change this at any time. To
bypass this prompt next time, supply name, the first argument to ionic start.

? Project name: myApp

そうするとつらつら自動でダウンロードしてくれるので、完了後作成したフォルダへ移動します。

% cd myApp

フォルダ構成

tabsコンポーネント

tabs.page.htmlとtabs.page.scssの二つを変更します。

tabs.page.html

修正前

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="goToPictures()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">


    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
      <ion-badge *ngIf="new_activities">{{new_activities}}</ion-badge>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

修正後

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="yourCustomFunction()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

tabs.page.scss

最初は何も書いてありませんが、下記のように変更します。


ion-tabs{
	ion-fab {
		margin-bottom: env(safe-area-inset-bottom); /* fix notch ios*/
		ion-fab-button {
			--box-shadow: none;
		}
	}
	ion-tab-bar {
		--border: 0;
		--background: transparent;
		position: absolute;
		bottom: 0;
		left:0; right: 0;
		width: 100%;
		&:after{
			content: " ";
			width: 100%;
			bottom: 0;
			background: var(--ion-color-light);
			height: env(safe-area-inset-bottom);
			position: absolute;
		}
		ion-tab-button {
			--background: var(--ion-color-light);
		}
		ion-tab-button.comments {
			margin-right: 0px;
			border-top-right-radius: 18px;
		}
		ion-tab-button.notifs {
			margin-left: 0px;
			border-top-left-radius: 18px;
		}
		svg {
			width: 72px;
			margin-top: 19px;
			path{
				fill:  var(--ion-color-light);
			}
		}
	}
}

結果

アイコンにメッセージを追加している部分はこのように書いています。

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

元ネタだとちゃんと条件分岐をしていました。

<ion-badge *ngIf="new_message">{{new_message}}</ion-badge>

参考文献

https://forum.ionicframework.com/t/ionic-5-awesome-tab-bar-with-curve/186579

新しいMacでXcodeの証明書を引き継ぐ

1

新しいMacでXcodeでアプリ開発をするための設定についてまとめます。

  • 以前のMacは必要なく、新しいMacだけで完結します

まず、ブランクのアプリを作ってみることから始めます。そうすると、Signing&Capabilityの部分でTeamを選べない問題に直面します。

そこで、これをまず解決する必要がりますので、Add an Accountというボタンを押して、Apple IDでサインインします。

そうすると、一応ビルドはできるようになります。しかし、そのままだとCodeSignの「キーチェーンログインのパスワードを入力してください」でパスワードを繰り返し求められて先に進みませんでした。

そこで、https://developer.apple.comからApple Developerにサインインして証明書をダウンロードしました。

ここで、該当するProfileをダウンロードしてダブルクリックしたら、動作するようになりました。

こんなに簡単だったっけって感じです。。あとで落とし穴がないか怖いです。

MacのXcodeのデベロッパーライセンスを更新するときにやること

1

Xcodeのデベロッパーライセンスを更新するときにやることをまとめておく。

まず、Appleからライセンス切れる旨を伝えるメールが来るので購入する。

次に、Apple Developerにログインして、Certificates&Identifiers & ProfilesのDevicesを選ぶ。

Continueを選ぶ。下記のようにデバイスリストを最新にしてくださいというメッセージが表示される。

Now that you’ve started a new membership year, make sure your device list is up to date. Once you complete this process, new devices can be added.

Select the devices you’d like to keep for this membership year and deselect the devices you’d like to remove. Only your currently enabled devices are shown and all previously disabled devices will be removed automatically. To keep a previously disabled device, visit the All Devices page, enable the devices you’d like to keep, and return to this page.

Continueを押下

この画面の「I acknowledge that any devices I disable during this membership year will continue to count towards my total registered devices.」にチェックを入れてResetする。

私は以上だけでした。

Macの環境設定の備忘録

1

データ分析とWebアプリ開発をしているエンジニアのMacの設定方法です。Mac book Pro 2019,2020でそれぞれBig SurとCataliaにて設定したものを備忘録として書いたものです。

  • CapsLockでの英語/日本語の切り替え
  • スクロール方向の切り替え
  • スクリーンショットの拡張子変更
  • aliasの設定
  • 一般ソフト(Chrome,Gimp,FileZilla,Atom,VScode)のインストール
  • Tableau Publicのインストール
  • node.jsのインストール
  • Ionicのインストール
  • Xcodeのインストール
  • CocoaPodのインストール
  • Pythonのインストール
  • GitHubのssh設定

CapsLockでの英語/日本語の切り替え

CapsLockで英語/日本語の切り替えができるようにします。

  1. デスクトップの左上にあるAppleのマークからシステム環境設定を選び
  2. 「キーボード」を選択し
  3. 上に並んでいるタブのうち、左から4番目の「入力ソース」を選択し
  4. ウィンドウの下にある「CapsLockでABC入力と切り替える」のチャックを打つ

スクロール方向の切り替え

タッチパッドの指の動かし方と画面の遷移方向を設定します。

  1. デスクトップの左上にあるAppleのマークからシステム環境設定を選び
  2. 「トラックパッド」を選択し
  3. 上に並んでいるタブのうち、左から2番目の「スクロールとズーム」を選び
  4. 「スクロールの方向:ナチュラル」のチェックを外す

スクリーンショットの拡張子変更

デフォルトのpngだと容量が大きすぎるので、jpgに変更して容量が小さくなるようにします。

defaults write com.apple.screencapture type 拡張子

で拡張子を指定すれば変更できるので、

defaults write com.apple.screencapture type jpg
killall SystemUIServer
exit

とします。jpgをpng,pdf,tiffなど任意のものに変更可能です。

aliasの設定

terminalにaliasを設定して、任意のコマンドを自分好みのコマンドで打てるようにします。

使用シェルがbashなら~/に.bash_profileと.bashrcを作成し、zshなら.zshrcと.zshprofileを作成し設定します。ログイン時に実行されるのは.bash_profileと.zshprofileで、.bashrcと.zshrchはターミナル起動時にいつも実行されます。

私はBig Surだったので、.zshrcに

alias o='open ./'
alias p='pwd'
alias l='ls'
alias n='cd /Users/masaya/Desktop;open /Users/masaya/Desktop'

と書きました。設定を反映するために再起動をするか

source ~/.zshrc

します。

一般ソフト(Chrome,Gimp,FileZilla,Atom,VScode)のインストール

いずれも公式サイトにアクセスして、ファイルをダウンロードすれば特に設定なしで使えるものです。

項目
Chrome普通にインストールします。dmgファイルをインストールしてから、ダブルクリックで起動して画面の操作にしたがってアイコンを移動するだけです。インストールあと、dmgファイルはゴミ箱に移動です、
VScodehttps://azure.microsoft.com/ja-jp/products/visual-studio-code/からダウンロード
Atomhttps://atom.io/からダウンロード

その他、Vivaldi,Firefoxなども同じようにインストールします。

Tableau Public

データ可視化用にTableau Publicを入れます。これも公式からダウンロードしてぽちぽちするだけです。

FileZillaのインストール

Macで無料で使えるFTPソフトです。

node.jsのインストール

Ionicのインストール

AndroidStudioのインストール

https://developer.android.com/studioからインストールします。

Xcodeのインストール

AppStoreから最新をインストールします。11GB程度と非常に重いので注意。

証明書関係が面倒ですが、以前のメモには下記のように書いてあります。

めちゃくちゃ苦労した。全て記録に残せたわけではないが、少なくとも以前のMacは必要なかった。AppleDeveloperサイトに行って、ログインする。Certificates, Identifiers & ProfilesからPlatformのカラムがALLのもののうち、DevelopmentとDeistributionを選んで、ダウンロードしてキーチェーンに登録する。これを新しい方でダブルクリックして登録する。それと、app developer idでパソコンでログインして、新しいMacを登録する。そうするとチームが選べるようになる。基本的には以上の二つでOK.ってこんだけのことだったっけ?

CocoaPodのインストール

https://cocoapods.org/に基づいて

sudo gem install cocoapods

でインストールします。

Pythonのインストール

以前はAnacondaを使っていましたが、有料化したので使うのはやめました。有料のAnacondaをインストールしたいなら、公式からダウンロードしてぽちぽちすればいけます。

https://www.anaconda.com/products/individual

GitHubのssh設定

Github用に .sshをホームディレクトリに配置して設定します。

ホーム画面で.sshディレクトリを作成します。

% mkdir ./.ssh
masaya@local-host ~ % cd .ssh 

鍵を作成します。

%  ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/masaya/.ssh/id_rsa): /Users/masaya/.ssh/id_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/masaya/.ssh/id_rsa.
Your public key has been saved in /Users/masaya/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:chw/VNuyHQ9D0DLooAVRfu9KtEK4TXoOVh36Gkw2c9U masaya@local-host
The key's randomart image is:
+---[RSA 3072]----+
|      o+.  .oo.  |
|       .o ..o=.  |
|       ooo+ +oE  |
|      .o B.+ + = |
|      o S * o . .|
|       & * +     |
|      = * + .    |
|     . + = .     |
|        o .      |
+----[SHA256]-----+

権限を読み取り専用に変更して

 chmod 600 id_rsa

情報をコピーして

% pbcopy < ~/.ssh/id_rsa.pub

GitHubに行って、アイコンをクリックしてから「settings」 > 「SSH and GPG keys」 > 「New SSH Key」と進んで、情報を貼り付ける

あとは下記で疎通できることを確認します。

% ssh -T git@github.com
The authenticity of host 'github.com (13.**.40.48)' can't be established.
RSA key fingerprint is SHA256:********pJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.***.40.48' (RSA) to the list of known hosts.
Hi N****! You've successfully authenticated, but GitHub does not provide shell access.

https://qiita.com/unsoluble_sugar/items/14bea376d8e6fce82eb3

設定でよく使うコマンド

macOSでデスクトップ表示 :「Fn」+「F11」

隠しファイルの表示;command shift .

Macにionicをインストールしてサンプルプロジェクトを作成する

1

クロスプラットフォームアプリケーションのフレームワークであるIonicをMacにインストールする方法について紹介します。

まず、node.jsをインストールする必要があります。インストールしていない方はこちらに最も簡単なインストール方法を記載していますのでご覧ください。

インストール方法

npm経由でのインストールとなりますので、sudo npm install -g @ionic/cliコマンドを打ちます。

% sudo npm install -g @ionic/cli
Password:
⸨   ░░░░░░░░░░░░░░░⸩ ⠇ loadDep:yallist: sill resolveWithNewModule smart-buffer@

ionic -vでバージョンが表示されたらインストール完了です。

% ionic -v
6.12.3

アプリを作ってみる

ionic startコマンドで簡単なアプリを作ってみます。

 % ionic start

Pick a framework! ?

Please select the JavaScript framework to use for your new app. To bypass this
prompt next time, supply a value for the --type option.

? Framework: (Use arrow keys)
❯ Angular | https://angular.io 
  React   | https://reactjs.org 

今回はAngularを選びます。プロジェクト名やCapacitorがいるかなど聞かれますので、入力します。CapacitorはIonicのプロジェクトをAndroid,iOS,デスクトップアプリとして動作させる環境(ランタイム)です。Web用なら入りませんが、今回は入れておきます。

? Project name: my-first-app

Let's pick the perfect starter template! ?

Starter templates are ready-to-go Ionic apps that come packed with everything
you need to build your app. To bypass this prompt next time, supply template,
the second argument to ionic start.

? Starter template: tabs
✔ Preparing directory ./my-first-app in 1.93ms
✔ Downloading and extracting tabs starter in 998.19ms
? Integrate your new app with Capacitor to target native iOS and Android? (y/N) 
y

ダウンロードしたら、プロジェクトへ移動します。

% cd my-first-app/

ionic serveコマンドでビルドしてみます。

% ionic serve    
> ng run app:serve --host=localhost --port=8100

アプリケーションが立ち上がったら完了です。

参考文献

https://ionicframework.com/docs/intro/cli

MacにFileZillaをインストールする

Macで無料で使えるFTPソフトであるFIleZillaをインストールする方法について解説します。

まず、公式サイトからFileZillaの圧縮ファイルをダウンロードします。

https://filezilla-project.org/download.php?type=client#close

FileZilla_3.51.0_macosx-x86.app.tar.bz2というファイルがダウンロードされますので、ダウンロード先でダブルクリックして解凍します。

下記のようなアイコンのついたものが作成されます。

これがアプリケーション本体なので、このファイルをコピペして、アプリケーションフォルダへ移動して完了です。

普通にダブルクリックしたら起動するので、あとはサーバ情報を入力して接続します。

Macでnode.jsをインストールする

1

日本ではhomebrewでインストールする方法ばかりが紹介されていますが、海外ではほとんど情報がなくマイナーな方法です。node.jsの公式からダウンロードしてインストールする方法が多く紹介されていますが、こちらの方が簡単なので今回はその方法を紹介します。

installerを公式から落とす

https://nodejs.org/en/にアクセスしてinstallerをダウンロードします。

LTSと書いてあるものがサポートの長い安定版です。

ダウンロードしたら、ファイルをクリックして起動させます。

途中、インストール先が表示されるのでメモっておきます。あとは画面の指示に従って進みます。完了したらインストーラはゴミ箱に移動していいですかと聞かれます。これが表示されたらもうこれだけでインストール完了です。

動作確認

ターミナルで下記のように打ち込んでバージョンが表示されたらOKです。

% node -v
v14.15.1

もしバージョンが表示されない場合、下記のコマンドを打って現在のパスを通している場所を表示させ、インストーラで保存先に指定していた場所が入っているかどうかを確かめます。

% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

簡単なプログラムを書いてみる

node.jsがちゃんと動作しているか簡単なコマンドで調べてみます。

% node
Welcome to Node.js v14.15.1.
Type ".help" for more information.
> console.log('hello node.js')
hello node.js
undefined

ちゃんと動作していることが確認できたら、control + cを2回打って終了します。

GCP(Google Cloud Platform)の特徴を解説。主要サービスの紹介も。

GCP(Google Cloud Platform)は、GoogleがGmail,Youtubeなど一般向けに開発しているサービスのバックエンドで使われているクラウド基盤を外部に公開したものです。GoogleはGCPですべてのサービスを運用しており、同じ環境を安価に利用することができます

「Datacenter as a Computer」という言葉に集約されるように、GCPではGoogleの世界中にあるコンピュータをまとめて一つのコンピュータのように扱えるようにするというコンセプトがあり、エンジニアに取って使いやすいサービスとなっています。

歴史

GCPは2008年に公開されたGoogleAppEngineが元になっており、IaaSとして始まったAWSとは異なり当初はPaaSとして導入されました。その後、2011年にGoogleCloudSQL ,GoogleCloudStoragが導入され、2013年にIaaSであるGCEが導入され現在の主要サービスがそろいました。

東京リージョンが開設されたのは2016年であり、まだまだ日本では導入後まもないサービスです

基本概念

GCPではユーザー、課金アカウント、プロジェクトという3つの基本概念があります。ユーザーとは、Googleのサービスを利用するときに利用しているIDのことです。GCP専用のアカウントは存在せず、GoogleアカウントにGCPの情報を紐づけることとなります。プロジェクトとは、GCPの中で機能やサービスを束ねるためのものです。プロジェクト内では各サービスの連携は容易ですが、プロジェクトを跨ぐことは基本的にはできません。さらにプロジェクトには1つの課金アカウントが設定されている必要があり、こちらに対して請求が来る事になります。

コンポーネントについて

GCPには様々なサービスがありますが、代表的なものを下記にまとめました。

分類名称機能
コンピューティングComputeEngineクラウド上で仮想マシンを立ち上げられます。OS以上のレイヤーを自由に設定できる一方で、環境構築やメンテナンスを自分でやらなければならないため上級者向けです。
コンピューティングAppEngineアプリケーションを運用してくれるPaaSサービス。GUIのアプリのホスティングサービス。基本的にはリクエスト受け取り後、60秒以上の処理はできない。ローカルファイルへのアクセスは禁止されている。
コンピューティングCloudFunctions関数ベースでGUIを伴わないサービスについてはこちらで十分。関数プログラムをホスティングできるサーバレスなコンピューテング環境
分類名称機能
ストレージCloud Storageストレージサービス。データ保存だけではなく、HTMLや画像などの静的なデータを直接Webに公開することも可能。バケットと呼ばれるコンテナで管理される。ファイルやフォルダと言ったGCS上のデータはオブジェクトと呼ばれる。オブジェクト数に上限はないが、1オブジェクト5TBまで。
ストレージSQLMySQLなどのリレーショナルデータベース
ストレージFirestoreNoSQLサービス
ストレージBigtableスケーラブルなNoSQLサービス
ビッグデータBigQuery低コストなビッグデータ解析用サービス。SQLでデータ取得可能。読み出しに強いのがSQLとの違い。

その他、様々なAPIが提供されています。

特徴

AmazonやMicrosoftと比較したGoogleの特徴の一つは、インフラを自社で保有していることです。Googleでは、データセンタ、光ファイバ回線などを独自で設計し導入しています。世界規模のクローズトネットワークを保有している数少ない事業者がGoogleですが、これによりサーバ間のデータ転送は極めて高速で可用性に優れているという特徴があります。また、元々、世界中の個人に対してサービスを提供してきた背景から、スケーラビリティと大規模処理に非常に強く大量のデータを保有しているために機械学習などのモデル精度に優れています。日本ではAWSが最も勢いに乗っていると思いますが、独自のサービスやデータに強いというところで一定の存在感を持ったサービスであり続ける可能性が高いです。

サービスアカウントとメンバーについて

https://cloud.google.com/iam/docs/service-accounts?hl=ja

GCPではメンバーとサービスアカウントという概念があります。メンバーはプロジェクトに登録した個々のGoogleアカウント(ユーザーアカウント)とサービスアカウントであり、サービスアカウントはアプリケーションなどで使用される特別なアカウントです。リソースへのアクセスの際はサービスアカウントを介する必要があります。

重要なのは、サービスアカウントを作成するとメンバーにも登録されることです。メンバーを登録してもサービスアカウントは作成されないです。サービスアカウントの方が強いイメージです、

もし証明書を介してリソースにアクセスする際、下記の手順を踏みます。

  • サービスアカウントを作成する
  • そのサービスアカウントはメンバーとしても登録されているので、メンバーの画面から権限を編集
  • サービスアカウントの画面から証明書の発行

という流れになります。確証はありませんが、証明書の権限は発行された際のメ権限を反映しているような挙動になりました。そんなはずはないですが、でもそれで少し詰まりました。おそらく多少はタイムラグがあるということかもしれないです。

Angular(Ionic)で非同期通信を実装する

1

フロントエンドアプリケーションフレームワークであるAngular(Ionic)で非同期通信を実装する方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。AngularはReactと異なりフルスタックなので、この辺の安心感はReactにはるかに勝ると思います。

Angular公式サイトの説明

ソースコードの素性

ソースコードの素性ですが、Ionic(Aunglar)のblankアプリから実装しました。なお、バックエンドのサンプルコードも作成しましたが、こちらはPythonのFlaskを使っています。Flaskでホスティングした http;//127.0.0.1:5000 のURLを叩いてデータを取ってくるアプリです。
GitHubにコードあげたのでそのまま動作確認できると思います。

環境

フロントエンド

Angular CLI: 10.0.5
Ionic CLI : 6.10.0
Node: 12.18.0

バックエンド

Python 3.7.3
Flask 1.1.2
Flask-Cors 3.0.9

コード

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あるけど、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

これでOK.anyを指定してしまっていますので書き方知っていたら教えてください。

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

AngularでWebAPIからblob形式で返された画像データを取得する方法

1

フロントエンドアプリケーションフレームワークであるAngular(Ionic)を使って、非同期通信で画像を読み込む方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。画像の場合は、さらにresponseTypeに’blob’形式を指定し、DomSanitizerでサニタイズして読み込みます。HTML側は<img [src]=imageUrl>で行けます。

AngularはReactと異なりフルスタックなので、この辺の安心感があります。

参考にしたサイト

stackOverflow

ソースコード

githubはこちら

解説

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あリますが、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

これでOK。anyを指定してしまっていますので書き方知っていたら教えてください。

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

【保存版】Flaskで画像を操作する際の6パターンを書き分ける

1

Flask上で画像を操作する際のソースコードのパターンについてですが、 画像の生成元をどうするかというもので3パターン、画像の最終的な処理方法をどうするかというもので2パターンで(組み合わせで)合計6パターンがあると思います。Flaskで画像処理を実装しようとするとこれらを書き分ける必要がありますのでそれについて記載します。

画像の生成元の3つとは

  • HTMLからFlaskで立てたURLへ直接画像がアップロードされる場合、
  • Flaskの関数の中でmatplotlibなどで生成する場合、
  • クラウドないしはローカルフォルダなどに保存されている画像を読み込んでくる場合

の3つです。

最終的な処理方法として2つあるというのは、

  • 画像をクラウドないしはローカルフォルダなどに保存する場合と
  • クライアント側へ画像をreturnする場合

の2つです。メタ情報だけ抽出して文字列で返す場合などもあると思いますが、これはJSONなどでリターンすればOKなので上記6パターンで対応できると思います。

Flaskで画像を操作したいと思うと、この6パターンについてソースコードを書き分ける必要があり、かなり大変です
しかし、幸いなことに、画像をどこかに保存する場合とクライアント側へ返す場合については画像をメモリ上でBlob形式であらかじめ保存することができれば同時に対応できますので、事実上かき分けなければならないのは3パターンとなります。
この記事では、画像をメモリ上でBlob形式で書き出すコードを使いながらこの3パターンについて整理したいと思います。

なお、ここでは処理後の画像の保存先としてGoogle Cloud Storageを利用した例で説明しますが、適宜AWSのS3、ローカルのフォルダの場所などと読み替えて下さい。今回記載するサンプルコードは下記の3つです。

1. 画像をFlaskの関数内で生成し、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
2. 画像をクライアントから受け取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す
3. 画像をGSCから読み取り、メモリ上でBlob形式で保存したのちにGCSへ保存しクライアント側へ返す

画像をFlaskの関数内で生成し、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    buf = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )
if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

ポイントは2つあって、一つ目はGUIに対応していないFlask上でmatplotlibが動作するようにmatplotlib.use(‘Agg’)というコードを入れていること二つ目はsavefigの部分でメモリ上でファイルを書き出していることです。ローカルで動かす場合は、普通にHDD(SSD?)にjpgなどで書き出しても問題ないと思いますが、クラウドなどだとローカルへのアクセスが禁止されていることが往々にしてあります。この場合に備えて、メモリ上でファイルの読み書きを行うioモジュールを使ってメモリ上に画像を出力しています。ここは個人的に結構ポイントです。

それでは次に既に保存されている画像を取ってくる場合です。

画像をGSCから読み取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

クラウドからデータを持ってきます。重いデータなどは予め画像にしておくと便利ですのでよく使っています。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './for-dev-268800-ed3aecf9b014.json'
    storage_client = storage.Client()
    bucket = storage_client.get_bucket('for-dev-268800.appspot.com')#"'for-dev-268800.appspot.com'

    source_blob_name = "path/to/your/file_on_gcs.jpg"
    
    #検索する場合はここを使う
    """
    blobs = bucket.list_blobs(prefix="")
    for blob in blobs:
        print(blob.name)
        print(dir(blob))
        print(blob.content_type)
        source_blob_name = blob.name
        content_type = blob.content_type
    print(inspect.getfullargspec(bucket.list_blobs))
    """    

    blob = bucket.get_blob(source_blob_name)
    buf = io.BytesIO()
    blob.download_to_file(buf)
    buf.seek(0)

    destination_blob_name = "path/to/your/file_on_gcs.jpg"
    blob = Blob(destination_blob_name, bucket)
    blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

    return send_file(
        buf,attachment_filename=source_blob_name,as_attachment=True
    )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについてはブラウザなどで直接叩いたいただければ問題ありません。

画像をクライアントから受け取り、メモリ上でBlob形式で保持したのちにGCSへ保存とクライアント側へ返す

最後のパターンです。これはクライアントから画像を受け取る場合ですが、要するにユーザがブラウザから画像を選択してアップロードするよく使うやつです(curlでこれをコード化すると、画像の一括アップロードなどにも使えます)。

# coding: utf-8
import os
import io
import time
import string
import random
import datetime
import numpy as np
from PIL import Image
import cv2
from flask import Flask, render_template, request, redirect, url_for, send_from_directory,send_file
from flask_cors import CORS
from google.cloud import storage
from google.cloud.storage import Blob
import inspect
import numpy as np
import cv2

app = Flask(__name__)
CORS(app)

@app.route("/", methods=["GET", "POST"])
def hello():   
    if request.files['image']:
        filename = request.files['image'].filename
        content_type = request.files['image'].content_type
        # 画像として読み込み
        stream = request.files['image'].stream
        img_array = np.asarray(bytearray(stream.read()), dtype=np.uint8)
        img = cv2.imdecode(img_array, 1)
        is_success, buffer = cv2.imencode(".jpg", img)
        buf = io.BytesIO(buffer)
        #buf = io.BytesIO()
        buf.seek(0)

        os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './path/to/your/credential_goole.json'
        storage_client = storage.Client()
        bucket = storage_client.get_bucket('your_backetname.com')
        blob = Blob(filename, bucket)
        blob.upload_from_string(data=buf.getvalue(), content_type=content_type)

        return send_file(
            buf,attachment_filename=filename,as_attachment=True
        )

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

上記ファイルを実行しFlaskサーバが立てたあと、こちらについては画像をHTMLからアップロードする必要があります。
APIを叩いてcanvas要素に描写するHTMLも載せておきますので、こちらを参考にしてください。buf変数に読み込んだものをOpenCVなどで操作すれば簡単な画像処理ソフトになると思います。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <title>bootstrapとjquery</title>

  </head>
  <body>

<main>
<article>
  <section>
       <input type="button" value="Start" onclick="main();"/>
        <canvas style="height: 30vw;width:50vh"></canvas>
  </section>
</article>
</main>

<script language="javascript" type="text/javascript">

    function main(){
      var canvas = document.getElementsByTagName('canvas');
      var ctx = canvas[0].getContext('2d');

      var img = new Image();
      img.src = 'http://0.0.0.0:5000';//FlaskでホスティングしたURL

      img.onload = function() {
        img.style.display = 'none'; // ようわからん
        console.log('WxH: ' + img.width + 'x' + img.height)

        ctx.drawImage(img, 0, 0);
        var imageData = ctx.getImageData(0, 0, img.width*2, img.height*2)

        for(x = 0 ; x < 1000 ; x += 10) {
          for(y = 0 ; y < 1000 ; y += 10) {
             ctx.putImageData(imageData, x, y);
          }
        }
      };    }
</script>

  </body>
</html>

curlコマンドでForm送信は代替できます。
HTMLのform送信をcurlコマンドで代替する方法はこちら

Flaskで画像を処理場合のソースコードはパターンごとに書き分ける必要があり、普段は目の前のタスクを終わらせて終わりがちですが、今回は6つのパターンについてまとめました。AWSのS3を使う場合やローカルで完結させる場合も同様に処理できると思いますので、ぜひ参考にしていただけるとうれしいです。

この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:

Matplotlibで作成したグラフをクライアントへ返す方法についてさらに詳細をまとめたのはこちら

https://qiita.com/NP_Systems/items/9bea70ade73cc48cbdf0

curlコマンドでHTMLのformでの画像送信を代替する

1

curlコマンドでHTMLのformでの画像送信を代替する方法です。
curlコマンドを打つときの場所にna18_1920x1080_221804.jpgと言う画像を置いた場合です。

結論

url -X POST -F 'image=@./na18_1920x1080_221804.jpg' http://127.0.0.1:5000/ --insecure

追加

ヘッダー情報とかクエリパラメータを引き渡したい場合はこちら。

curl -X POST -H 'Host:some_destination.com.' -H 'Authorization:something' -F 'image=@./na18_1920x1080_221804.jpg' -F "arg1=myarg1" -F "arg2=myarg2" http://127.0.0.1:5000/ --insecure

Get通信の場合

ちなみに、画像送信とは関係ないですが、Get通信の諸々のサンプルこちらです。

curl  -H 'Host:some_destination.com.' -H 'Authorization:something' -X GET  "http://127.0.0.1:5000?arg1=myarg1&arg2=myarg2" --insecure

Windowsの場合の補足

Windows10のcurlコマンドだと、実際はinvoke-webrequestというコマンドのエイリアスなので動作しません。その場合、本物のcurlの実行ファイルをダウンロードして、パスを通して実行ファイル名を書き換えて、それでコマンドを打てば大丈夫です。

Xserverのメールを他のメールソフトで見られるようにする

1

ここでは、Xserverのメールを他のメールソフトで見られるようにする方法として、iPhoneのGmailで設定する方法を紹介します。

手順

Gmailを起動して、右上の自分のアイコンをクリックします。このような画面になります。

そしたら、「別のアカウントを追加」を押下します。

その他(IMAP)をタップし、そこで登録したいメールアドレスを入力します。

さらにボタンを押すと、下記の画面になり、ここでサーバとパスワードを入力します。これは受信サーバと送信サーバの両方が必要になり、最初に表示されるのは受信サーバです。

パスワードとサーバ名を入力します。

パスワードはXserverの管理画面からメールアドレスを発行する際に、メールアドレス に対して発行するものなのでもしこれがわからない場合は作り直すか作った人に聞くしかありません。

サーバー名を入れて終わりです。ポート番号は基本的には既に入力されているはずですが、変える場合は下記を参照してください。

POP/IMAPサーバー名
(ホスト名)
設定完了メールに記載されている「メールサーバー」を入力してください。※サーバーパネル内「サーバー情報」に記載の“ホスト名”と同一です。例)sv***.xserver.jp
SMTPサーバー名
(ホスト名)
設定完了メールに記載されている「メールサーバー」を入力してください。※サーバーパネル内「サーバー情報」に記載の“ホスト名”と同一です。例)sv***.xserver.jp
ユーザー名
(アカウント名)
メールアカウントの追加にて設定したメールアドレス(ドメイン名を含む)を入力してください。

例)user@example.com
例)info@example.com
パスワードメールアカウントの追加にて指定したパスワードを入力してください。
ポート番号
POPの場合
995 (SSLを利用しない場合は 110)
ポート番号
IMAPの場合
993 (SSLを利用しない場合は 143)
ポート番号
SMTPの場合
465 (SSLを利用しない場合は 587)

そうすると、頑張って設定をしてくれます。

送信サーバの設定画面に移行するので、同じ情報を入力して完了です。

まとめ

Xserverのメールを他のメールソフトで見られるようにする方法として、iPhoneのGmailで設定する方法を紹介しました。Xserverのリンクはこちら。

https://www.xserver.ne.jp/manual/man_mail_setting.php

個人・少人数のシステム開発にはAngularがオススメ

1

Angularとは

 GoogleのドキュメントやGCPで使われているAngularは、Googleが中心開発している JavaScript フレームワークです。AngularJSと混同されていている方もいらっしゃいますが、AngularJSのメリットを生かしたまま互換性のない形で生まれ変わったのがAngularです。Angularは、SPA (Single Page Application)と呼ばれる技術を用いて、従来のWebでは実現できなかった機能を提供する技術です。イメージ的には、従来のWebページよりアプリケーションよりのものを作りやすくなると感じています

SPAの特徴

 最近、従来のWeb技術では考えられなかったようなサイトが増えてきています。たとえば、オフラインでも動作するWebページやGoogleのドキュメントのように同じURLなのに検索ワードによって違う内容が表示されるページなどです。SPA (Single Page Application)ではindex.htmlだけを読み込み、そのあとはDOMの操作をフロントエンドで行って擬似的な遷移を行っています。これによりオフラインでも画面遷移ができたり、検索クエリによって動的にページを書き換えたりといったことができます。

SPAフレームワークにおけるAngularの特徴

 SPAのフレームワークはいくつかあります。Facebookの開発しているReact.jsなどが有名ですが、個人的にはReact.jsよりも断然Angularがおすすめです。Reactが最小限の本体とサードパーティのライブラリで構成されているのに対して、Angularは開発に必要な機能をすべて盛り込んだフルスタックです。開発に必要なデータバインディング、フォームバリデーション、非同期通信、ルーティング、テスタビリティ、セキュリティへの配慮など、一通りの機能がすべてあらかじめ用意されているので、Angular を選択することでコーディングに集中できます。ライブラリが複数あるとどれを使えばいいのか迷いますしライブラリ間の依存関係などを気にする必要が出てくると思いますが、そういった懸念から解放されコーディングに集中できることがAngularのメリットだと考えています。個人的にはAngularは「控えめにいって最高」のフレームワークです。

クロスプラットフォームなのもよい

わたしは個人でシステム開発をしているのですが、iOS用にSwiftで、Android用にKotlinで、そしてWeb用にJavaScriptで開発をしていたらいくら時間があってもたりません。そこで一つのソースコードでさまざまなプラットフォームで動作すればありがたいわけです。そこでクロスプラットフォームの仕組みとして「Ionic 」「Cordova」「NativeScript」「React Native」Electron」「Capacitor」「Xamarin」「Unity」などが候補にあがるわけですが、Angularは「Capacitor」というMacデスクトップアプリ、Windowsデスクトップアプリ、Web、iOS, Androidに出力できるこれまた最高のフレームワークと組み合わせることができます。したがって、Angularは効率的にプロジェクを作成し、それを複数のプラットフォームに同時に出力できるという夢のような生産性を実現するフレームワークです。

AngularJSとの非互換性について

Angularの前身であるAngularJSがあまりにも有名なため、AngularJSの終了から5年弱経っているのにまださまざまなところで混同されています。しかし、AngularはAngularJSで顕在化した問題を解決するべく、内部構造から細部に至るまで再設計され実装されています。

一般的にフルスタックフレームワークだと、開発規模が大きくなるにれバンドルされるファイルサイズが大きくなりパフォーマンス面で悪影響を与えることもあります。AngularJSでも顕在化したその問題を解決するために、AngularではJavaScript VM (Virtual Machine) フレンドリーな実行コードを生成することで実行速度を改善したり、AoT コンパイル(Ahead-of-Time Compilation)と呼ばれるビルド時に表示を高速化するための仕組み、さらに段階的にソースコードをよみこむLazyLoadingと呼ばれる仕組みを利用して高速化する方法も提供されるなどです。最初にAngularで開発するときに訳が分からなくなってしまいがちですが、AngularJSとは互換性がないということを覚えておくといいと思います。半年毎にバージョンが変わるのですが(例えばAngular8が現在のバージョンなら、半年後はAngular9がでる)これらには一部の機能を除いて互換性があります。

まとめと結論

SPAの技術の一つであるAngularについてまとめました。近年普及が急速に進んでいるPWAアプリ作成のフレームワークとして代表的な物で、一つのコードで複数のプラットフォームに出力できるクロスプラットフォーム性について記載しました。

Angularで個人で作ったアプリがあります。OCRという写真から文字を抜き出す技術を使って、ブログなどを早く書くためのツールです。これも一つのコードでAndroid ,Web,iOSに出力できるので大変便利でした。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)のフォルダ構成を完全解説

1

Ionicのフォルダ構成について

 Ionicで作成したプロジェクトは下記のような構造になっています。

directory

 かなり複雑で、最初に見たときは戸惑うかもしれません。実際に触るのはsrc/appフォルダがほとんどになりますが、他のフォルダを含めて解説したいと思います。

ルートフォルダについて

名前説明
e2e/システム全体の動作検証を行うE2Eテストの際に利用します。最初はほとんど触りません
node_modules/node.js上で動くため、package管理ソフトにnpmを使うことになりますが、npm経由でインストールしたライブラリが保存されます
src/実際に開発を行う場所です。9割このフォルダを触ります。
angular.jsonAngularの設定ファイルです。フォルダの出力先などビルドを中心にした設定はここで行います。
package.json実際にプロジェクトで使用するライブラリを指定します
package-lock.jsonpackage.jsonで指定したものが入っているか確認します。エラーの時にたまにいじります

したがって、基本的にはsrcだけをいじると考えておいて差し支えないと思います。

srcフォルダについて

それでは、実際に開発を行うsrcフォルダを見てみます。

srcのなかでも触るのはsrc/appがほとんどになりますが、こちらについても簡単に解説をします。

ファイル/フォルダ説明
src/app実際に触るのはほとんどこのフォルダのなかです。このフォルダが開発対象と考えていただいて問題ないと思います。
src/assets画像や音声、動画などのリソースを配置します。例えばsrc/resourcesというフォルダを自分で作成すると読み込まれないので注意が必要です。
src/environmentsなかには簡単なjsonファイルが本番用と開発用に入っています、環境変数をそれぞれ定義します。Google Analyticsをいれるときなどにいじりました。
src/themecssの拡張であるscssでデザインを規定しますが、そのファイルが入っています。IonicのUIが優れているため、個人的にはあまり触りません。
src/global.scssプロジェクト全体に適用するscssを書く際はこちらを使います。たた、実際はpageごとに用意されるscssをいじることが多いです。
src/index.htmlAnugular(Ionic)でも最初によみこまれるのはIndex.htmlです。
src/main.tsアプリを起動するためのファイルです。index.htmlのあとに読み込まれます。
src/polyfills.tsIE10/
11対応を行うもののようですが、IonicってIEで見れないんですよね。。
src/test.ts単体テストの設定ファイルです

まとめ

基本的に、src/appのなかをいじる、とだけ考えておけばいいと思います。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

pandasで条件抽出する2つの方法(単一条件、複数条件)

1

pandasで条件抽出する方法には、queryメソッドを使う場合と使わない場合がある。

A列に1-100、B列に101-200、 C列に201から300のデータが並んでいる100行のデータを考える。

df = pd.DataFrame({
    "A":[i for i in range(100)], 
    "B":[i+100 for i in range(100)], 
    "C":[i+200 for i in range(100)],])

メソッドを使用しない場合:

#A列が20未満を抜き出す
df = df[(df["A"]<20)]
#A列が20より大きく、かつ、50未満を抜き出す
df = df[(df["A"]>20)&(df["A"]<50)]
#A列が20より大きく、または、50未満を抜き出す
df = df[(df["Α"]>20)|(dF["B"]<50))

複数条件の場合、かっこで個々の条件を囲む必要がある。

queryメソッドを使用する場合:

単一条件での抽出:

#Aが20未満を抜き出す
df = df.query('A< 20')
df = df.query('(A > 20) and (A < 50)')
df = df.query('(A > 20) | (A < 50)')

個人的にはqueryを使わない方が好みです。

特定の文字列を含む行を取得したい場合はこちら

Angularでfirestoreのコレクションからデータ一覧を取得する

1

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

1

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Ionic(Angular)でズーム可能なグラフをchart.jsで作成する

1

概要

Ionic(Angular)でマウスホイールで拡大や移動などが可能なグラフを作成します

方法

標準のchart.jsに加えて、charjs-plugin-zoomを導入します。

通常のchart.jsの導入方法はこちら。

https://top.np-sys.com/general/438/

これをズーム可能にするためにcharjs-plugin-zoomを導入します。

npm i chartjs-plugin-zoom -s

そしたら、グラフを作成しているpageのtsファイルにて(今回はsrc/app/tab2/tab2.page.tsでした)

import 'chartjs-plugin-zoom';

を上部に追加して、あとはOptionsのなかに

          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },

のように指定するだけです。

冗長ですが、コードを貼り付けておきます。貼り付ける場所などの確認にご使用ください。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';
import 'chartjs-plugin-zoom';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  @ViewChild('myChart') myChart;
  @ViewChild('myChart2') myChart2;
  @ViewChild('myChart3') myChart3;
  bars: any;
  colorArray: any;
  myNum:number;
  data:any=  {
        type: 'line',
        data: {
  				labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'Aug'],
  				datasets: [{
  					label: 'My First dataset',
            backgroundColor: 'rgb(150, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(150, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
  						12,
  						14,
              12,
  						14,
              12,
  						41,
              12,
  						4,
  					],
  					fill: false,
  				}, {
  					label: 'My Second dataset',
  					fill: false,
            backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
              2,
  						4,
              2,
  						4,
              2,
  						4,
              20,
  						142,
  					],
  				}]
  			},
        options: {
          scales: {
            yAxes: [{
              ticks: {
                beginAtZero: true,
                min: 0,
                max: 200
              }
            }]
          },
          responsive:true,
          maintainAspectRatio: false,
          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },
          }
      }

      title = 'app';

      columnDefs = [
          {headerName: 'Make', field: 'make' },
          {headerName: 'Model', field: 'model' },
          {headerName: 'Price', field: 'price'},
          {headerName: 'Make', field: 'make2' },
          {headerName: 'Model', field: 'model2' },
          {headerName: 'Price', field: 'price2'},
          {headerName: 'Make', field: 'make3' },
          {headerName: 'Model', field: 'model3' },
          {headerName: 'Price', field: 'price3'},
      ];

      rowData = [
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 }
      ];

  constructor(
  ) { }

  ionViewDidEnter() {
    this.createBarChart(this.myChart);
    this.createBarChart(this.myChart2);
    this.createBarChart(this.myChart3);
  }

  onClicked(){
    let v = window.innerWidth;
    alert(v);
  }


  createBarChart(canvasId) {
    this.bars = new Chart(canvasId.nativeElement, this.data);
  }

}

Ionicにag-gridでエクセルライクな表を導入する

1

背景

Webで表をいれたいときってあります。主に3つの方法があると考えております。

・Tableを使う

・FlexBoxを使う

・ライブラリを使う

今回は、ライブラリを使う方法としてAg-gridを導入します。先頭の二つは簡単な表には使用できると思いますが、例えば1万行を超えるようなデータを表にして、Excelのようにセルごとに編集して色分け、、などをするためにはAg-gridを使うのが最適だと思います。

https://www.ag-grid.com/

無料でもできる範囲はかなりひろいです。

環境

$ ionic info
Ionic:
   Ionic CLI : 6.4.1
Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9
System:
   NodeJS : v12.10.0
   npm    : 6.14.2
   OS     : macOS Mojave

流れ

ライブラリをnmpでインストールしたあと、プロジェクト全体のレイアウトを管理するglobal.scssで読み込みます。その後、使いたいページのpage.module.tsで読み込んだ後、page.tsで情報を設定して、page.htmlにタグを設置します。

参考文献

https://www.ag-grid.com/angular-grid/

インストール

パッケージのインストールを行います

npm install --save ag-grid-community ag-grid-angular

すると、node_modulesにライブラリが配置されます。

scssの読み込み

Angularではstyles.scss に書き込みますが、ionicではglobal.scssに書き込みます。

@import "../node_modules/ag-grid-community/src/styles/ag-grid.scss";
@import "../node_modules/ag-grid-community/src/styles/ag-theme-alpine/sass/ag-theme-alpine-mixin.scss";

.ag-theme-alpine {
    @include ag-theme-alpine();
}

あとは、これを使いたいページのモジュールで読み込むだけです。ag-gridはモジュールなので、tsファイルで読み込む必要はありません

表を作る

以上で準備完了です。あとは使いたいpageで読み込みます。下記はsrc/app/app.module.ts、src/app/app.component.ts,src/app/app.module.htmlの場合ですが、src/app/tab1/tab1.module.tsなどでも同じです。その場合、src/app/app.module.tsには設定する必要はないです。

それではsrc/app/app.module.tsから編集します。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AgGridModule } from 'ag-grid-angular';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AgGridModule.withComponents([])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Copy

次にsrc/app.component.tsで情報を設定します。

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {
    title = 'app';

    columnDefs = [
        {headerName: 'Make', field: 'make' },
        {headerName: 'Model', field: 'model' },
        {headerName: 'Price', field: 'price'}
    ];

    rowData = [
        { make: 'Toyota', model: 'Celica', price: 35000 },
        { make: 'Ford', model: 'Mondeo', price: 32000 },
        { make: 'Porsche', model: 'Boxter', price: 72000 }
    ];
}

最後にapp/app.component.htmlファイルでタグを設置します。

<ag-grid-angular
    style="width: 500px; height: 500px;"
    class="ag-theme-alpine"
    [rowData]="rowData"
    [columnDefs]="columnDefs"
    >
</ag-grid-angular>

そうすると、下記のような表が表示されるはずです。

all-in-one-migrationでインポートしてログインできなくなった

タイトルの通り、サーバ上のデータをexportしてxammpのローカルにインポートしたらログインできなくなった。

’http://localhost:8080/phpmyadmin/’でデータベースの情報を見たら、メールアドレスとして使用していたユーザー名が変わっていた。メールアドレスは独自ドメインのものを使っていたのだが、@以降がlocalhostに変わっていた。

また、site-guardと干渉することもあるということなので、import/export時にはそのプラグインをオフにした方が良さそう。

独自ドメインからローカルへ

site-guardをオフにする

export/importする

’http://localhost:8080/phpmyadmin/’でユーザ名を書き換える

ということになりそう。あまり普段からやる感じでもなくなるけど。

https://webst8.com/blog/wordpress-all-in-one-migration/

Pandasでcsvを読み込む

高機能なデータハンドリングが魅力のPythonですが、Pandasを使用すると複雑な処理を1行でできるので便利です。よくわからないけど、とりあえずPythonでデータを操作したいというなら、Pandasでデータを読み込めば間違いありません。

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]汎用的なデータの読み込みならPandasがおススメ![/chat]

そもそもPandasってなんだっけ?

Pandasは、データハンドリング用で使われる最も有名なライブラリのひとつです。
Pythonは本体には最小限の機能しか備えておりません。パソコンにソフトをインストールしてできることを増やすように、Pythonは、追加のパッケージ(ライブラリ)をインストールして様々なことができるようになります。Pandasは、データ処理用のライブラリです。データの読み込みは様々な方法でできますが、最も覚えることが少なく、汎用的な手法として、Pandasでのデータ読み込みがおススメです。
例えばPandasは下記のようなときに使用できます

・Excelデータの読み込み
・CSV形式の読み込み
・txtデータの読み込み

どうやってインストールするの?

Pandasのインストール方法は、大きく分けて二つあります。一つは、Pythonの本体に単独でインストールする方法。もう一つは、Anacondaを利用する方法です。Anacondaとは、よく使用するパッケージをPython本体と一緒にダウンロードできる拡張版Pythonのイメージです。Anacondaは下記からインストールできますが、その場合、すでにPandasは自動でインストールされていますのでそれ以上の環境構築は必要ありません。

pipでインストールする場合:

pip install pandas

Anacondaでインストールする場合(おすすめ):
https://www.anaconda.com/で最新版をインストールすればOK.他の言語でもいえることですが、追加ライブラリのバージョンは、合わせておかないと動きません。これを依存関係といいますが、これらを整えて配布されているのはAnacondaです。Continuum社の有料サービスの無料版というのも安心です。

ホップ:Pandasでのcsvデータを読み込むには?

それでは、実際にPandasでcsvのデータを読み込みます。コードをGitHubにあげておくので、そちらからダウンロードかまいません。web上でPythonを実行できるpaizaのページからコードを貼り付けて実行していただいてもいいと思います。
paizaのページ:https://paiza.io/projects/featured?language=python3

GitHub;https://github.com/awesomerainbows/wp_read_csv

もし初めての場合、どこに何をおいたらいいかわからないと思います。そのときは、読み込みたいcsvとpythonのファイルは同じ階層においてください。おススメは、デスクトップにtestというフォルダを作成して、そこにpythonファイルとcsvの2つを配置することです。
こんな感じです。

Pythonコードは下記です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv")#ファイル名は、クオテーションで囲んでください。もしtest.csvという名前なら、"hogehoge.csv"は、"test.csv"です

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]全然読み込めないんだけど[/chat]

[chat face=”man1″ name=”” align=”right” border=”gray” bg=”none” style=””]ファイル名のあとに読み込む条件を指定すれば読み込めるよ[/chat]

読み込むことはできましたでしょうか。
整形されたデータの場合はこれで読み込めたはずです。
ところが、実際扱うデータは往々にしてエラーを吐いてしまいます。
その場合、下記の項目をチェックして、読み込む条件を指定して読み込んでみてください。

エラーが出る場合、ポイントは下記になります。列名(header)が先頭にあるかどうか
行番号(index)がデータに含まれるかどうか
列名のないデータだけのファイルかどうか

列名(header)が先頭にない場合の読み込み

この場合、下記のコードで1行目と2行目をスキップして読み込みますので下記になります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",skiprows=2)#skiprowsの引数を指定することで先頭の2行を無視します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

[chat face=”man1″ name=”” align=”left” border=”gray” bg=”none” style=””]df = pd.read_csv(“hogehogehoge.csv”,header=2)と列名を指定しても読み込めます[/chat]

[chat face=”man1″ name=”” align=”right” border=”gray” bg=”none” style=””]0から数えるから、Excelでいう3行目は2で指定するんだね[/chat]

行番号(index)がデータに含まれる場合

この場合は、1列目はindexであると明示的に指定します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",index_col=0))#indexは0列目だと明示します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です
列名がデータにない場合

この場合、1行目からデータであるということを指定する必要があります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
df = pd.read_csv("hogehoge.csv",header=None))#header=Noneでカラム名はないことを明示します

print(df)#読み込んだデータをすべて表示
print(df.head())#最初の5行程度を表示
print(df.shape)#読み込んだデータの行数と列数を表示。100行2列なら(100,2)です

列名を作成しながら読み込みたい場合、

df=pd.read_csv(“hogehoge.csv”,header=None,names=[“a”,”b”,”c”,”d”])

のようにnamesの引数で指定します

その他の場合

その他さまざまな条件を指定してデータを読み込むことができますが、詳細は公式ドキュメントを参照していただくのがよいと思いますので、最後によく使う指定条件をまとめたいと思います。

◎ファイルが大きすぎる
⇒nrows:nrows=100のように読み込む行数をint型で指定
◎なぜかエラーが起きる
⇒engine:pythonで読み込むかCで読み込むかを指定。engine=”python”と指定すると柔軟に読み込める
◎日本語が文字化けする
⇒encoding:encoding=”shift-jis”で読み込んでみる

よろしくお願いします。
以上、お疲れさまでした。

CordovaでjavaScriptを1行ずつデバッグする

1

Chrome Remote Web Inspector によるデバッグがよさそう。
参考にしたサイトはこちら。

https://mobilefirstplatform.ibmcloud.com/tutorials/ja/foundation/8.0/application-development/debugging-applications/#debugging-with-chrome-remote-web-inspector

半日ほど迷った。ポイントは、アンドロイドの端末で「usbデバッグを有効化」することと、アプリ側にもやり取りを許可するためのソースを埋め込まないといけないこと。ソースはJavascriptではなくて、Javaなので、どこに書くか迷った。結局追加プラグインのファイルにJavaのファイルがあったので、そこに張り付けた。

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ WebView.setWebContentsDebuggingEnabled(true); }

一度実行されたらchromeとの接続には支障がないとのことで、プラグイン呼び出しのメインの関数のなかに記載した。なお、JAVAのファイルの中でパッケージをインストールする必要があるので、そのファイルの上のほうで下記コードも書き加えた。

import android.os.Build;import android.util.Log;import android.content.pm.ApplicationInfo;import android.webkit.WebView;

import android.util.Log;import android.content.pm.ApplicationInfo;import android.webkit.WebView;

書き加えたのはcordova-plugin-admobproプラグインのなかの
AdMobPlugin.javaというファイルで、

@Override protected void pluginInitialize() { super.pluginInitialize(); // TODO: any init code }という部分を

@Override protected void pluginInitialize() { super.pluginInitialize(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ WebView.setWebContentsDebuggingEnabled(true); }; // TODO: any init code }と書き換えた

Extract android app’s package name

windows10

if you are able to use use adb commands already, then you can get package name as below.

extract android app's package name

matplotlibで日本語を表示(python)

matplotlibで日本語を扱いたいとき、フォントをダウンロードして、matplotlibrcファイルを修正して、、みたいな方法が正統派なのかもしれない。
でも職場のパソコンに一斉にそういう設定ができますか?みたいなときがある。

1.フォントをダウンロードして、C:\Users\**\Anaconda3\Lib\site-packages\matplotlib\mpl-data\fonts\ttfの中にコピペをする
2.ファイルのなかにigfont = {‘family’:’IPAexGothic’}という文字列を追加しておく

これだけでよさそう。これなら自動でできそう。
ちなみにつまったところとして、コピペするファイルはipaexg.ttfとipaexm.ttfの2つだということ。一方だけでをコピペするだけだとうまくいきませんでした。フォントのダウンロード先は下記。

https://ipafont.ipa.go.jp/node26

Pythonでwordpressに自動で投稿する(python-wordpress-xmlrpc)

1

絹田
絹田

プラグインやライブラリをPython側に入れるかWordPres側に入れるかで2通りの方法があるよ

自動投稿する際,プラグインやライブラリをPython側に入れるかWordPres側に入れるかで2通りの方法があります.

  • REST:WordPressの「WP REST API」プラグインを使用する
  • XML-RPC:WordPressにデフォルトで備わっているwordpresのxmlrpcのインターフェースを利用する

RESTの方が汎用性は高い一方で、テーマによっては使えないことがあります。XML-rpcの場合は昔からあるので追加設定はいらず安定感はあります。Pythonを使ってxmlrpcでWordPressサイトを操作する場合、python-wordpress-xmlrpcをpipすれば使えるようになります。私はWordPressのプラグインを増やしたくなので後者の方を利用するようにしています。

環境

動作環境はWindows10 64bitでPython3.6.5(Anaconda3.5.1-0)。 Mac Python3.8とXserverのターミナル(Python3.8)からもうまく動作しました。

コード

pip install python-wordpress-xmlrpc

でライブラリを入れました。
(conda install ***だとファイルが見つからなかったので、 Anaconda環境であったがpipで入れました)

あとは下記のコードを実行するだけです。

新規に投稿する場合


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPosts, NewPost
def main():
    """
    変数を定義
    """
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    which="publish"
    #which="draft"
    #下書きに投稿するか本番で投稿するか選択

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id,password)
    post = WordPressPost()

    """
    実際に投稿する
    """
    post.post_status = which
    post.title = "タイトルをここに書く"
    post.content = "本文をここに書く"
    post.terms_names = {
    "post_tag": ['希望するtag1', '希望するtag2'],
    "category": ['希望するカテゴリー1', '希望するカテゴリー2'],
    }
    #過去に投稿した記事としたい場合、投稿日をここで指定。例として2018年1月1日10時5分10秒に投稿した例を示す。
    post.date=datetime.strptime("2018/1/01 10:05:10","%Y/%m/%d %H:%M:%S")
    wp.call(NewPost(post))

if __name__=="__main__":
   main()

編集する場合

編集をする場合、編集したい記事のIDを取得したあと、その記事に対して操作します。

1.まず編集したい記事のIDを取得


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPost, GetPosts, NewPost, EditPost

def main():
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id, password)
    post = WordPressPost()
    posts = wp.call(GetPosts({'number': 5}))
    for post in posts:
        print(post.id, post.title,str(post.terms[0]))

if __name__=="__main__":
   main()

もし編集したい記事が決まっている場合、その記事の編集画面にいくとURLにIDが表示されますのでそちらを利用することも可能です。

2. 記事を編集する

上記で記事IDを調べて、その記事に対して操作します。pidが編集したい記事のID(post_id)です。


# -*- coding: utf-8 -*-
import time
from datetime import datetime
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.users import GetUserInfo
from wordpress_xmlrpc.methods.posts import GetPost, GetPosts, NewPost, EditPost
def main():
    id="******"
    password="******"
    #idとpasswordはwordpressの管理画面に入るためのもの

    url="https:***/xmlrpc.php"
    #第3者が閲覧するURLの後ろに/xmlrpc.phpをつける。
    #ワードプレスの管理画面の後ろにつけるとエラーになった

    """
    クライアントの呼び出しなど
    """
    wp = Client(url, id, password)

    pid = 1030
    post = wp.call(GetPost(pid))

    post.post_status = "publish"
    post.content = "content"
    post.terms_names = {
    "post_tag": ['tag1'],
    "category": ['diary',],
    }
    ret = wp.call(EditPost(pid, post))

if __name__=="__main__":
    main()

動作環境の注意

手元のPCはWIndowsでも Macでも動作確認しています。Xserverのターミナルからもうまく動作しました。しかし、以前Google Cloud PlatformのCloud functionsでデプロイした場合はできなかった記憶があります。AWS lamdbaなどでもエラーが起きる可能性があるのでローカルで動作させる事をお勧めします。

動作しない場合

・siteGuardなどセキュリティ系のプラグインを入れると、デフォルトでxmlrpcをオフにしますので、解除が必要です

・Xserverなど一部のレンタルサーバでは意図的にデフォルト でアクセスをOFFにしていますので管理画面から設定変更が必要です

siteguardの設定をオフにする

ダッシュ画面の左下からsiteguardを選択します。その中にXMMRPC防御という項目があるのでクリックします。

左上の部分でOFFを選択します。

Xserverの場合

xserverの場合、公式が丁寧に説明してくれているのでこちらを見れば大丈夫だと思います。

https://www.xserver.ne.jp/news_detail.php?view_id=6875

https://www.xserver.ne.jp/manual/man_server_wpsecurity.php#link-b02

絹田
絹田

読んでくれてありがとう.これからもよろしくね

Angularで再読み込み時に404エラーがでる

1

なぜ404エラーとなるのか

AngularをはじめとするSPAでは、URLごとに実態のあるページが存在する一般的なサイトとは異なり、index.htmlをクライアント側で書き換えるため直接特定のアドレスにアクセスするとエラーがでます。

開発環境では出ない理由

開発環境だと開発サーバ側でindex.htmlを読み込んでくれているので問題にならないのですが、デプロイ時には自分で設定することになります。設定内容はファイルが見つからない時はindex.htmlを参照してくれというものですので、Angularではなくサーバ側のapacheやnginxなどの設定ということになります。なのでデプロイ時に顕在化する問題となります。

APacheの場合

.htaccessファイルに下記を指定します。サブドメインを使ってホスティングする場合、サブドメインのルート(サブドメインのindex.htmlのある場所)に下記を設置すればいいです。

<pre class="wp-block-syntaxhighlighter-code">RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html</pre>

Nginxの場合

こちらのページ(Front Controller Pattern Web Apps)に記載されているtry_filesを使ってindex.htmlに対して下記を設定します

try_files $uri $uri/ /index.html;

Firebaseでホスティングする場合

rootにある設定ファイル(確かfirebase.jsonだったと記憶)を下記のように直せばいいです

"rewrites": [ {
  "source": "**",
  "destination": "/index.html"
} ]

その他の場合

IISやRubyの場合は公式を見ていただくのが良いかと思います。

https://angular.io/guide/deployment#server-configuration

その他

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)でtabとサイドバーを備えたプロジェクトを作成する

概要

上記の画像のようなサイドメニューとタブバーを備えたプロジェクトを作成します。

流れ

IonicのデフォルトテンプレートのひとつであるTabプロジェクトにサイドメニューを追加します。

参考文献

https://petercoding.com/ionic/2019/05/05/side-menu-in-ionic4/

https://ionicframework.com/jp/docs/api/menu

準備 デフォルトのプロジェクトを作成する

下記のコマンドを打ってTabバーを備えたプロジェクトを生成します。

% ionic start sidemanu tabs --type=angular

さらにCapacitorを入れますか?(Integrate your new app with Capacitor to target native iOS and Android?) と聞かれたら、スマホアプリにすることを検討指定場合はYESにしておきます。

その後、作成したプロジェクトへ移動します。

$ cd sidemanu

プロジェクトへ移動したあと、下記コマンドで一度起動してみます。

ionic serve

このような画面が立ち上がればOKです。なお、デフォルトがIEなどのブラウザになっているとそもそも表示されないです。ChromeやEdgeでアクセスしてください。

なお、Tabバーのプロジェクト構成は下記となっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

これにサイドメニューを設置して行きます

サイドバーに配置するページを作成しておく

事前にサイドメニューに設置するページを作成しておきます。新しいターミナルを立ち上げてプロジェクトフォルダまで移動したあと、下記コマンドでpageを2つ追加します。

ionic generate page side/mySide1
ionic generate page side/mySide2

プロジェクトは下記のようになります。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── side
│   ├── my-side1
│   │   ├── my-side1-routing.module.ts
│   │   ├── my-side1.module.ts
│   │   ├── my-side1.page.html
│   │   ├── my-side1.page.scss
│   │   ├── my-side1.page.spec.ts
│   │   └── my-side1.page.ts
│   └── my-side2
│       ├── my-side2-routing.module.ts
│       ├── my-side2.module.ts
│       ├── my-side2.page.html
│       ├── my-side2.page.scss
│       ├── my-side2.page.spec.ts
│       └── my-side2.page.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

以上で準備は完了です。あとはこの2つのページへハンバーガメニューからアクセスできるようにします。

サイドメニューを設置

app.component.htmlでサイドメニューを作成し、ion-router-outletに登録する

Ionicは、サイドメニューを簡単に作成するためにion-menuと呼ばれるコンポーネントを提供しています。app.coponent.htmlの中にion-menuタグを使ってサイドメニューを作っておいて、それを<ion-router-outlet>の中でidで紐付けます。ion-router-outletタグはコンポーネントを埋め込むために使用するものですので、これに登録しておくと作成したサイドメニューを他のページから簡単に読み込むことができるようになります。

最初はapp.component.htmlは下記のようになっています。ion-router-outletタグに何も登録がありません。

<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>

これを下記のようにします。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="my-content">
    '''
    '''
    </ion-menu>
  <ion-router-outdlet id="my-content"></ion-router-outlet>
</ion-app>

こうすることにより、ion-menuタグの中に設定した中身を他のページから読み込むことができるようになります。私の場合、下記のように設定しました。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="content1">
        <ion-header>
          <ion-toolbar>
            <ion-title>Navigate</ion-title>
          </ion-toolbar>
        </ion-header>
        <ion-content>

          <ion-list lines="none">
            <ion-list-header>
              一般情報
            </ion-list-header>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side1" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="person"></ion-icon>
                <ion-label>
                  Privacy Policy
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side2" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="help"></ion-icon>
                <ion-label>
                  Terms of use
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

          </ion-list>
        </ion-content>
      </ion-menu>
  <ion-router-outdlet id="content1"></ion-router-outlet>
</ion-app>

公式にも実装例が載っています。

https://ionicframework.com/docs/api/menu

任意のページからのサイドメニューの読み込み

以上でサイドメニューを設置できましたので、tab1ページから読み込んでみます。tab1.page.htmlはデフォルトではこのようになっていると思います。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

hdearの中で下記のタグで読み込めるようになりますので

      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>

tab1.page.htmlを丸ごとこのように変更します。

<ion-header>
  <ion-toolbar>
      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>
    <ion-title>
      File selection
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

おまけ

サイドメニューから飛んだ先で戻るボタンを設置できます。

最初はこのようになっていると思いますが、

<ion-header>
  <ion-toolbar>
    <ion-title>mySide2</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

下記のようにion-back-buttonを設置します。

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>
      Privacy policy
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

そうするとこのように戻るボタンが設置できます。

Angular(Ionic)でグラフを描写する

1

グラフ描写のライブラリ

Ionicでグラフを描写するライブラリにはいくつかあるようですが、安定していてよく使われているchart.jsを使いことにしました。意外に簡単でびっくりしました。

インストール

プロジェクトのルートフォルダに移動して、Terminalで下記コマンドを実行します。

npm install chart.js --save

すると下記のようなメッセージが出てchat.jsがインストールされます。

+ chart.js@2.9.3
added 4 packages from 7 contributors, removed 1 package and audited 1527 packages in 12.531s

node_modulesのなかに格納されるので、これを使いたいページのtsファイルから呼び出すだけで使用できるようになります。

実装する

このような棒グラフを書いてみます。

参考サイト

https://enappd.com/blog/charts-in-ionic-4-apps-and-pwa-part-1/52/

HTMLファイルに要素を追加する

chart.jsはcanvas要素にグラフを描写するので、まずはグラフ要素をHTMLに記載します、

<ion-header>
</ion-header>

<ion-content>
=>  <canvas #myChart></canvas>
</ion-content>

描写する

あとはtsファイルからライブラリをimportしたあとにグラフを描写するだけです。HTMLの要素を取得するので、ViewChildも呼び出します。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.page.html',
  styleUrls: ['./admin.page.scss'],
})

export class AdminPage implements OnInit {

  @ViewChild('myChart') myChart;

  bars: any;
  colorArray: any;
  constructor() { }

  ionViewDidEnter() {
    this.createBarChart();
  }

  createBarChart() {
    this.bars = new Chart(this.myChart.nativeElement, {
      type: 'bar',
      data: {
        labels: ['Label1', 'Label2', 'Label3', 'Label4', 'Label5', 'Label6', 'Label7', 'Label8'],
        datasets: [{
          label: 'Viewers in millions',
          data: [25, 30, 50, 60, 92, 75, 100, 67],
          backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
          borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero: true
            }
          }]
        }
      }
    });
  }
}

pythonのリストをmap、filter、lambdaで操作する

1

操作対象のリストを作成

>>> lst = [i for i in range(10)]
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

map関数で全体に対して操作

こういうリストがあったときに全体に対して操作するならこれ

>>> lst2 = list(map(lambda e:e*2,lst))
>>> lst2
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

python3では、map関数が返すものはmap objectなのでlistへ変換する必要がある。

filter関数で特定のものを抽出

>>> lst2 = list(filter(lambda e:e%2==0,lst))
>>> lst2
[0, 2, 4, 6, 8]

Agular(ionic)で多言語化対応を行う

1

環境

Ionic:

 Ionic CLI: 6.4.1 (/Users/myName/.nodebrew/node/v12.10.0/lib/node_modules/@ionic/cli)
   Ionic Framework  : @ionic/angular 5.1.1
   @angular-devkit/build-angular : 0.901.6
   @angular-devkit/schematics: 9.1.6
   @angular/cli   : 9.1.6
   @ionic/angular-toolkit : 2.2.0

Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9

System:
   NodeJS : v12.10.0 (/Users/masaya/.nodebrew/node/v12.10.0/bin/node)
   npm    : 6.14.2
   OS     : macOS Mojave



参考のサイト

公式ドキュメントはこちら

https://github.com/ngx-translate/core

パッケージのインストール

npm install @ngx-translate/core @ngx-translate/http-loader --save

package.json

  "dependencies": {
    "@ngx-translate/core": "^12.1.2",
    "@ngx-translate/http-loader": "^4.0.0",

実装

app.module.ts

app.module.tsにて、TranslateModuleをHttpClientModule, HttpClientと一緒にインポートします.

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule} from '@ngx-translate/core';
・
  imports: [
    HttpClientModule,    
    TranslateModule.forRoot(),
  ],

使いたい場所のモジュール

使いたい場所のモジュールで

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

export function createTranslateLoader(http: HttpClient) {
    return new TranslateHttpLoader(http, '../../assets/i18n/', '.json');
}
@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
           provide: TranslateLoader,
           useFactory: (createTranslateLoader),
           deps: [HttpClient]
         }
      }),
    ],

使いたい場所のtsファイル

import {TranslateService} from '@ngx-translate/core';
 constructor(
    private translate: TranslateService,
  ) {
    this.translate.setDefaultLang('en');
    this.translate.use('en');
}

あとHTMLに

<p>{{ 'HELLO' | translate }}</p>

とかく

assetに情報を配置

最後に

/assets/i18n/

を作成して、jsonファイルをおけばいいです。

{
  "HELLO": "こんにちはEnglish"
}

複数の場合はこのように書いてください

{
  "Main": "メイン",
  "File selection": "ファイルの選択",
  "1. Choose a file": "1. ファイルの選択",
  "Pick a file from your storage": "ストレージから選択",
  "Or take a new picture": "新しく写真を撮る",
  "2. Crop your image": "2. 写真の切り抜き",
  "select the area you want to analyze from the photo": "解析したいエリアを選択してください",
  "No ads 3 times if you watch a short video": "広告が3回表示されなくなります!",
  "Preparing for the analysis. Since this app uses service that need to be payed(from not you but me), an ad will be shown to you.":"安全な方法でアップロードします。 このアプリは有料のAPIを使用しているため、広告が表示されます。",
  "Analyzing.":"分析しています。"
}

最後のブロックの後にカンマを入れるとエラーになりますので注意.

app.component.htmsも多言語化したい場合

app.module.tsを下記のようにします.

+import {HttpClientModule, HttpClient} from '@angular/common/http';
+import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';

+export function createTranslateLoader(http: HttpClient) {
+    return new TranslateHttpLoader(http, '../assets/i18n/', '.json');
+}

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
+    TranslateModule.forRoot({
+      loader: {
+         provide: TranslateLoader,
+         useFactory: (createTranslateLoader),
+         deps: [HttpClient]
+       }
+    }),
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.component.ts

+import {TranslateService} from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor(
+    private translate: TranslateService,
  ) {
+      this.translate.setDefaultLang('en');
+      this.translate.use('en');
  }
}

Ionic(Angular)のライフイベントについて

1

ライフイベント、ライフサイクルとは

ライフイベント、ライフサイクルとは、ページを読み込み、表示し、その後ユーザーが離脱する一連の流れの中で特定のタイミングで発火するDOMイベントのことです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。

これにより、ページが読み込まれたらこれをする、ページに変化があったらこれをするといったことを実装できます。

Angularのライフイベントについて

 それまでまずはAngularのライフイベントからです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。個人的にはAngularのイベントを使う頻度の方が高いです。

こちらの公式ドキュメントに細かく記載されておりますので、ここではよく使うライフイベントについて転載します。

https://angular.jp/guide/lifecycle-hooks

イベントフック(これを関数のように書いて呼び出します)概要
ngOnInit()ページやコンポートネントが最初に完全に読み込んだら実行されるものと理解しています。イメージではJavaScriptのonDOMcontentLOaded()です。その後、 ディレクティブ/コンポーネントを初期化します。ページ読み込みの際に1度だけ呼び出されます。
最初 の nOnChanges() の後に 一度 呼び出されます
ngOnChanges()Angular がデータバインドされた入力プロパティを(再)設定するときに応答します。 このメソッドは、現在および以前のプロパティ値の SimpleChanges オブジェクトを受け取ります。
ngOnInit() の前に呼び出され、データバインドされた入力プロパティが変更されるたびに呼び出されます。
ngOnDestroy()Angularがディレクティブ/コンポーネントを破棄する直前に、クリーンアップします。 メモリリークを回避するためにObservableの購読を解除し、イベントハンドラをデタッチしましょう。
Angularがディレクティブ/コンポーネントを破棄する 直前 に呼び出されます。

Angular場合、ライフサイクルはコンポーネントの生成や破棄がトリガーになります。そのため、最初の1回しかイベントを実行したくないときなどに利用します。

なお、constructorとngInitを同じように考えがちですが、前者はライフイベントではありませんので、コンポーネントが表示されるまで待ってくれません。そのため、constructorを実行したのになにも反映されないということがあります。constructorではページ操作を行うような動作は入れない方が良いです。

Ionicのライフイベント

 次にIonicのライフイベントです。

イベントフック(これを関数のように書いて呼び出します)概要
ionViewWillEnter()ページが表示されるアニメーションが始まるとき
ionViewDidEnter()ページが表示されるアニメーションが終了したとき
ionViewWilLeave()ページから離脱・遷移するアニメーションが始まるとき
ionViewDidLeave()離脱するアニメーションが終了したとき

Willのタイミングでデータの描画を行うのはよくないです。Will を使用する場合はデータの取得などの準備、Didを使用するのはデータを表示するなどのタイミングがよさそうです。

 Angularの場合、先に述べたようにコンポーネントがトリガーとなっているのに対して、lonicのライフライクルトリガーはユーザーにとっての表示です。なのでページ遷移して戻ってきたときにも発生するなどでUIの改善などの観点では非常に使いやすくなっています。

ライフイベントの実装方法

Ionicの場合、ライフサイクルイベントを関数のように追記するだけですが、Angularのライフサイクルイベントは先に型を定義する必要があります。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionicの忘備録

Observableなオブジェクトの作成

import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private client:HttpClient
  ) { }  
・
・
devTestObservable(myStr):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(myStr+'myObservable');
      }, 1000);
    })
  }

  devTestPromise() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('myPromise');
      }, 1000)
    })
  }

  devTestHttp():Observable<any> {
    return new Observable(observer => {
        this.client.get('/assets/data/test.json').subscribe(e =>{
          observer.next(e);
        })
    })
  }

Copy

  
import { DataService } from '../tabs/data.service';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private dataService:DataService,
    private client:HttpClient,
  ) {
      this.myStr = 'first ';
  }
・
・
async onButtonClick(){

    this.dataService.devTestHttp().subscribe(e =>{
      console.log(e,'Hi');
      this.myStr = e.authAdmin.linkLoginButton+'Hi';
    })

    this.dataService.devTestObservable().subscribe(e =>{
      console.log(e);
    })

    this.dataService.devTestPromise().then(e =>{
      console.log(e);
    })

  }

Copy

input

<ion-item lines=’full’><ion-label position=’floating’>らべる</ion-label><ion-input type=’text’></ion-input></ion-item>

inputには、最初にplaceholderが表示されていて、フォーカスをあてるとplaceholderのラベルが左上に移動するものが一番いいです。

こんな感じになります。

フォーカス前

フォーカス後

ボタン

横いっぱいに広がったよく見るボタンが表示されます。クリック時の関数は(click)=で指定します。

<div><ion-button expand=’block’ (click)=”someFunc()”>Button</ion-button></div>

新しい部品をつくる

ionic g

とだけ入力すれば、下記の選択肢を表示してくれます。

まだオンプレミスで消耗してるの?Firebaseについて

サーバを立てるのは常識だった

 以前はなにかサービスを作ろうと思うと、サーバを立てて運用するというのが常識でした。物理的なサーバを購入して自社や自宅に置くことはもちろんですが、そうでなくてもVPSで借りて運用するなどは常識だったと思います。WebならPHPでのバックエンドになりますし、アプリの開発ならRestAPIなどを設計したでしょうか。いずれにせよ、サーバを借りてプログラムを書くという点は常識でした。

サーバの運用は大変

 しかしサーバの運用は大変なものです。プログラムを設置し、アクセスを適切に受けるようにして、セキュリティも考慮しなければなりません。OSの設定も大変ですし、物理的なサーバなら電源の冗長化や放熱なども考慮する必要があります。わたしも昨年、会社でDellのWindows Serverを購入しましたが、初期投資は150万円かかりましたし、場所を確保したり専用クーラを設置したり導入までに3ヶ月程度はかかりました。

クラウドがあるじゃないか

 こうした負担を軽減する方法はないのか?と考えた時にクラウドがあります。

クラウドはGoogleやAmazon、Microsoftなどがネット上に構築したサービス群で、OSをVPNのように提供する低レベルなものから(IaaS)、サービスだけを提供するSaaSレベルのものもあります。そして必要なものを選んで利用するほうが、自前でサーバを運用するより圧倒的に楽です。

従来と比べて大きく違うのは、まず初期コストはゼロというです。そして、サーバを運用し続ける負担がほぼなくなります。インフラの部分の担当を任せられるのですから非常にらくになります。これにより爆発的な導入がなさされておりますが、「セキュリティ」を理由に導入していないところも多くあると思いますが、官公庁ではAWSを使ったり、NetFlixのような非常に大規模サイトでも使用されています。

その他、個人的に感じるクラウドの強みは、開発者が納得いくまで何度でもやり直せる点です。先ほど述べたように、わたしは昨年DellのWindows Serverを購入しましたが、「とりあえず動くようにプログラム設置できたら、あとは怖いから放置」ということになってしまっています。実際、一念発起しても杞憂に終わらなかったですし。。一方、クラウドはサービスなので何度でも自分が納得するまで設定をして消去ということを繰り返すことができます。テスト環境を作って、なっとくするまで設定する、そしてうまくいったものだけで本番環境で再現できるとなれば、取り合えず動くようになったら放置というオンプレミス環境と比べて大きくプログラムへの理解が深まります。

Firebaseとは

サーバレスを実現するクラウドサービスはいろいろありますが、特に利用されるのがFirebaseです。現在はGoogle(Alphabet)の傘下ですが、スタートアップだったものをGoogleが買収したものです。

Firebaseの機能

スマホアプリ開発におけるバックエンドを担うことがおおいようですが、JavaScript,JAVA,Swift,Objective-C、Swift、C++、Unity、Python、PHP、Goなどほとんどの環境からアクセスできます。

機能はこちらです

ユーザー認証非常にいやらしい認証周りを簡素化するFirebaseの代表的なサービスです。IDとパスワードで実現するものに加えて、GoogleやTwitter、 Facebookなどのアカウントを利用したユーザー認証や匿名認証も可能です。
データベース「キー=バリュー型」の非SQLデータベース(Json形式)です。SQLの使えるRDBは連携しているGCPで利用できるのでFIrebaseにはNoSQLが搭載されています。ソケット通信で接続しておいて、変更があったらフロント側のデータを自動で更新してくれるので非常に便利です
ストレージファイルの保管機能。普通に画像などをストレージできます
ホスティングWebサイトをデプロイし、公開します
クラウドファンクションネットワーク経由で特定の処理を呼び出し、実行します。Pythonで書くこともできます。このURLを叩いたらPythonプログラムを実行して解析した結果を返すといったことができます
機械学習キット言語翻訳、写真からのテキスト認識、顔認証などです
Google AnalyticsGoogle Analyticsによるアクセス解析です
メッセージングスマホアプリへのプッシュ通信を実現します。Niftyなどでもできるようですが、FIrebaseがメジャーかと。。

いくらかかるのか

実際はGCPと連携して使う場合もあり一概にはいえないですが、使い始めてすぐお金がかかるようにはなっていません。2種類の料金プランが用意されていて、まずは一ひとつめのSparkプラン(無料枠)から始めるようになっているためです。Sparkプランだけでもデータの保存に1GB/月、読み込みに10GB/月が設定され、認証は1 万/月可能なのでかなりでかいです。料枠を超えると利用量に応じた料金が自動的にかかる仕組みです。

公式によると下記の料金体系となっています。

プロダクト  無料Spark プランGenerous limits to get started従量制Blaze プラン大規模なアプリの料金を計算checkSpark プランの無料使用量を含む*
A/B テスト無料
Analytics無料
App Distribution無料
App Indexing無料
認証電話認証 – 米国、カナダ、インド help電話認証 – 他のすべての国 help他の認証サービス1 万/月1 万/月check$0.01/認証$0.06/認証check
Cloud Firestore保存データ下りネットワークドキュメントの書き込みドキュメントの読み取りドキュメントの削除合計 1 GiB10 GiB/月2 万/日5 万/日2 万/日$0.18/GiBGoogle Cloud pricing$0.18/10 万$0.06/10 万$0.02/10 万
Cloud Functionshelp呼び出しGB 秒CPU 秒アウトバウンド ネットワーキング12.5 万/月4 万/月4 万/月Google サービス専用$0.40/百万$0.0025/千$0.01/千$0.12/GB
Cloud Messaging(FCM)無料
Crashlytics無料
Dynamic Links無料
HostingGB 保存済みGB 転送済みカスタム ドメインと SSLプロジェクトごとに複数のサイト1 GB10 GB/月checkcheck$0.026/GB$0.15/GBcheckcheck
アプリ内メッセージング無料
ML Kithelpデバイス用 APIカスタムモデルのホスティング / サービス提供AutoML Vision Edge データセットAutoML Vision Edge トレーニング

Cloud Vision API
checkcheck画像 1,000 件/プロジェクト3 時間/プロジェクト

close
checkcheckGoogle Cloud pricing15 時間/請求先アカウント、
$4.95/時間$1.50/1,000
Cloud Vision の料金を参照)
パフォーマンス監視無料
Predictions無料
Realtime Database同時接続 helpGB 保存済みGB ダウンロード済みプロジェクトごとに複数のデータベース1001 GB10 GB/月close20 万/データベース$5/GB$1/GBcheck
Remote Config無料
ストレージhelpGB 保存済みGB ダウンロード済みオペレーションをアップロードダウンロード オペレーションプロジェクトごとに複数のバケット5 GB1 GB/日2 万/日5 万/日close$0.026/GB$0.12/GB$0.05/1 万$0.004/1 万check
Test Labhelp仮想デバイスでのテスト物理デバイスでのテスト10 件/日5 件/日$1/デバイス/時間$5/デバイス/時間
Google Cloud PlatformBigQuery およびその他の IaaS を使用 helpclosecheck
プランを選択Looking for the Flame plan?無料Spark プランすぐに開始可能従量制Blaze プランプランを選択

その他

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。フロントエンドはAngularで開発し、バックエンドはGCPとFirebaseを使っています。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Angularにおけるモジュール解説

1

モジュールとは

Anularは、コードをモジュールという単位で機能ごとに分割することで管理します。電気自動車が部品を組み立てることで比較的簡単に組み立てられるように、機能ごとにモジュールへ分割することでプログラムを作成しやすくなります。代表的なものとして、ルーティングや HTTP 通信などの機能を持つものなどがあります。

CommonModule

CommonModuleはNgIf や NgFor などの基本的なディレクティブやパイプを提供します。両者ともAngularを代表する機能ですが、Ngifは変数の条件によって(例えばログインしているかしていないかで)表示を変えるときに使用しますし、NgForはリスト構造になっているものに対してイテレートすることで劇的にソースコードを書く手間を低減できます。Angularを代表する機能であるモジュールのため直接読み込む必要はなく、BrowserModule やWorkerAppModule などの各プラットフォームごとのモジュールがエクスポートしているので、これらのモジュールを読み込んでいれば同時に利用可能になります。

Forms Module

Forms Moduleは、Template Driven なフォームを実装するのに必要となるディレクティブやプロバイダを提供します。Template Driven はフォームにユーザーが入力した情報によって変数側のデータを書き換える方法で、これによりJavaScriptからクラスやIDを指定してgetElementbyID()などの手間を省くことができます。Forms Module は、@angular/forms からインポートすることで利用できます。

src/app/app.module.ts

import { NgModule }      from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule }   from '@angular/forms';import { AppComponent }  from './app.component'; @NgModule({imports: [BrowserModule,FormsModule],declarations: [AppComponent,],providers: [],bootstrap: [ AppComponent ]})export class AppModule { }

Reactive FormsModule

ReactiveForms Module は、アプリケーションに Reactive Form を実装するために必要なディレクティブやプロバイダを提供します。Forms Moduleがユーザー側の入力でバックエンド側の変数を書き換える方法なのに対して、このモジュールはバックエンド側からフロントエンドを書き換える方法です。ReactiveForms Module は Forms Module と同様に、@angular/forms からインポートして利用します。

HttpClientModule

HttpClientModule は、HTTP リクエストや、レスポンスの処理を実装するのに必要なプロバイダを提供します。Pythonなどとは異なり非同期通信だるJavaScript(そしてSupersetであるTypeScript)では、Ajax通信をよく使いますが、Ajax通信をAngularで実装する際はこのモジュールを使用します。AjaxHttpClientModule は @angular/common/http からインポートすることで利用可能になります。

JsonpModule

JsonpModule は、JSONP を用いた HTTP 通信を実装するのに必要なプロバイダを提供します。JsonpModule は HttpClientModule と同様、@angular/http からインポートすることで、利用可能になります。個人的にはあまり使用したことはありません。

Router Module

AngularはSPAですので、実態は一枚のHTMLのDOMを書き換えてページ遷移を実現します。その際のURL変更に関する機能を担当するモジュールです。RouterModule は @angular/router からインポートすることで利用します。

BrowserModule 

BrowserModule は、DOM イベント(たとえばheaderの読み込みが完了したかなど(https://phpjavascriptroom.com/?t=js&p=event))やキーボードイベント・タッチイベントなど、主にブラウザアプリケーションの作成に必要な機能を提供します。これもWebからきている人には理解しやすい概念だと思います。なお、BrowserModule のなかでCommonModule もエクスポートしているため、BrowserModule をインポートするだけでCommonModule も読み込まれます。@angular/platform-browser からインポートして利用しますが、個人的には何も考えずにインポートするモジュールです。

ServerModule

ServerModule は、主にサーバーサイドレンダリングを行うアプリケーションを作成するために必要な機能を提供します。公式を見ると解説が省略されすぎているのですが。。

https://angular.io/api/platform-server/ServerModule

前述の BrowserModule もエクスポートしているので、BrowserModule で使える機能も利用できます。@angular/platform-server からインポートします。サーバ側はPythonで書くことが多いので、あまり個人的に使用したことはありません。

WorkerAppModule

これも個人的にはほとんど使ったことがないモジュールで、公式によると、Angular10から廃止されることが決まっておりますので使用しないほうがいいでしょう。

https://angular.jp/api/platform-webworker/WorkerAppModule

まとめと結論

Anularは、モジュールという単位で特定の機能を担うコードを分割し、使用する場合はImportしてすぐに使用できるようにしています。個人的には、

  • 存在をしっていればいいもの
    BrowserModule
  • 使う中でわかってくるもの
    Forms Module、Reactive FormsModule
  • 勉強して使えるようにならないといけないもの
    HttpClientModule

というイメージです。

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

HuaweiのApp galleryでアプリで提出してみた

1

背景

いま、スマホアプリって作っても全然インストールしてもらえないんですよね。もう市場飽和してて、10年前ならインストールされたのになんて思いなが開発していました。そんな矢先、Google Play storeからHuaweiが締め出されたという話があり、あっという間に5社で連合で新しいアプリストアが立ち上げられました。App Galleryです。これはチャンスでございます。なので手元のAndroidアプリをApp galleryでリリースしたのでその所感をお伝えします。

野良アプリストアでしょうか

まぁどうでしょうか。当面はそうでしょうね

今後はどうでしょうか

Huaweiの技術力、わたしはGoogleやAppleに劣らずすごいと思います。共産党はトップダウンでものごと進めるし、動画などがもっとも普及しているのが中国であることを鑑みると、今後のITを引っ張るのは中国だと思います。App galleryって「危険そう」「野良アプリ」ってイメージありますよね?でも今後、GoogleとAppleのストアに並ぶ市場になると思います。

所感


・全体的な印象は、中身はGoogle Play、見た目はApp Store Connect
・デベロッパー登録は無料。デベロッパー登録に1日、審査に4日程度。なので1週間程度あればアプリ出せる
・デベロッパー登録にパスポートと銀行のキャッシュカードが必要
・日本語はなくて英語か中国語。でも手順はGoogleとAppleより簡単
・Googleの.apkならそのまま出せる
・.apkならいいので、cordovaやunity,ionicなども問題なく出せる

デベロッパー登録にパスポートと銀行のキャッシュカードが必要

え?まじか。。この時点でかなり気分が冷める。。いろいろ心配になる。。しかし無視して強行突破。

日本語はなくて英語か中国語。でも手順はGoogleとAppleより簡単

これもかなりのハードル。英語重いなぁと思ったけど、中国語だと本当にわからないので英語のほうがありがたいと思う不思議。。まぁ、わかりやすいレイアウトなので、Google翻訳しておいて画面にしたがっていけば問題ないかと。最初どこからてをつければいいかわからなかったから苦労した。

方法

ここから作業していく。

https://developer.huawei.com/consumer/en/appgallery

開くとこんな感じ。

でupload your appかregisterで下記画面にいく。するとこう

国に日本を選択して、必要な事項を登録。携帯に認証コードが送られてくる。俺の場合、文字化けしていて焦ったが、resendすると文字コードが違うのか文字化けの解消されたメールが届くのでそれで認証すればおk。

で、パスポートとキャッシュカードを登録して審査が降りるのをまつ。審査は二十二時ごろにやって明け方には承認された。場合によっては、審査が通る前にとりあえず作業は進められますよ、という画面がでるので当日に進められる人はすすめばよい。その後Consoleへいくとこうなる

左上のMy appsを押す。こうなる。

右上のNewをおすとこうなる。

であとは画面通りに進めば良い。Googleで出したことあったら、それより入力項目少ないから、ここまでたどり着けばいけると思う。

注意したこと

・なにかあったときにGoogleのアプリに影響出したくなかったので、com.XXX.XXXの名前を別に作ってまったく別アプリとした

・なのでAdmobのアプリIDも変えた

・審査はちゃんとやってる。審査完了後こんなメールがきた。

Test Environment]: Wi-Fi networking, Android 9.0/EMUI 9.1(nova 5),Android 9.0/EMUI 9.1(p30), Multilingual settings.でテストしたとのこと

・apkをアップロードするのにエラーがなんども起きた。同じファイルなのにうまくいったり行かなかったり、時間も五分から十分かかった。かなりここで挙動を調べているように感じた。

App galleryに乗せたもののURLわからなかったからGoogleとAppleのをのせとくけどこれをアップした。写真から文字抽出して高速でブログ書くためのアプリ。使って欲しい。

https://apps.apple.com/app/id1497498494

https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

後日談

Google、Apple、Huaweiの3プラットフォームにアプリを公開してから半年が経ちました。Google Play、Apple のApp Store、App Galleryの売り上げですが、驚くことにApp Galleryが最も多いです。次がApp Storeです。App Galleryがまだブルーオーシャンなことがわかります。ただ、最も残念なのは個人開発者だと広告を打てないことです。広告売ったら結構凄そうかも。米国からの部品調達禁止もなんとか乗り切ってほしいです。

cordova-plugin-crypt-fileでcordovaをアップデートしたらビルドできない

1

cordova-plugin-crypt-fileを入れて難読化しているのだが、cordova9.0.0へバージョンアップしたらビルドできなくなった。

エラーメッセージは下記

Using "requireCordovaModule" to load non-cordova module "path" is not supported. Instead, add this module to your dependencies and use regular "require" to load it.

バグではなく、セキュリティを高めるための仕様の変更らしい。プラグインが対応すべき問題とのことで、プラグインを最新版にすることが基本の対策とのこと。しかし、今回原因となったcordova-plugin-crypt-file 1.3.3がまだ対応していなかった。

対象のプラグイン

アプリのソースコードを暗号化するcordova-plugin-crypt-file

対処方法

これを参考に/plugins/cordova-plugin-crypt-file/hooks/after_prepare.jsをいじることで解決した。

https://github.com/PeterHdd/cordova-plugin-crypto-file/issues/8

変更前

module.exports = function(context)
{
var path = context.requireCordovaModule('path'),
fs = context.requireCordovaModule('fs'),
crypto = context.requireCordovaModule('crypto'),
Q = context.requireCordovaModule('q'),
cordova_util = context.requireCordovaModule('cordova-lib/src/cordova/util'),
platforms = context.requireCordovaModule('cordova-lib/src/platforms/platforms'),
Parser = context.requireCordovaModule('cordova-lib/src/cordova/metadata/parser'),
ParserHelper = context.requireCordovaModule('cordova-lib/src/cordova/metadata/parserhelper/ParserHelper'),
ConfigParser = context.requireCordovaModule('cordova-common').ConfigParser;

変更後

module.exports = function(context) {

    var path              = require('path'),
        fs                = require('fs'),
        crypto            = require('crypto'),
        Q                 = require('q'),
        cordova_util      = context.requireCordovaModule('cordova-lib/src/cordova/util'),
        platforms         = context.requireCordovaModule('cordova-lib/src/platforms/platforms'),
        ConfigParser      = context.requireCordovaModule('cordova-common').ConfigParser;