システムを検討する難易度と金額

こんにちはmtjです。

自分たちの仕事ではお客さんの要望によってシステムを検討することが多々あります。
そして同時に金額も伝えるのですが その時の精度はそのシステムにどんな機能を付けるか、何が必要かを正確に把握できるかだと思います。

例えばレジの無いお店に 在庫、売上管理システムを入れたい場合にどういうシステムを入れるか
PSOシステムの知識があればまずPOSシステムをイメージすると思います。
そしてPOSシステムにどのような物が必要かをイメージできればそれを最初から作る工数をイメージすればある程度金額の予想はつくと思います。

上記のようにPOSシステムを知っていればそれらのパッケージ等を提案する事もできるでしょう
しかし知らない場合は回りくどい方法で作って無駄に金額が上がるかもしれません。
機能の把握が十分でなく 十分なシステムを提案できないかもしれません。

そういったシステムを提案できるようにするためにも日々IT系のシステム、技術を取り入れる事は大事だなと感じます。

GooglePlayストアページからアプリのバージョンを取得する方法【令和最新版】

はじめに
Mauiのアプリ開発が完了しました。

開発終盤に開発した機能に、
Androidアプリにて現在のアプリのバージョンがストアのバージョンよりも古い場合、
古いバージョンは使用して欲しくないのでアプリを終了し、ユーザに更新を促す処理があります。

処理は以下のような流れで、
0. アプリを起動
1. ストアのアプリバージョンを取得
2. 現在のバージョンと比較
3. 現在のバージョンが古ければ、ストアページに遷移してアプリを終了

この内の1の工程におけるGooglePlayストアからアプリのバージョンを取得する処理についての共有記事となります。
重要なことなので先に言っておきますと、
デバイスによりバージョンが異なるアプリ(大手のアプリの多く、YouTubeやGoogleChromeなどなど)の場合は取得できません。

先行事例
iPhoneのAppStoreアプリの場合、iTunesAPIによりバージョンが取得できるので、
GooglePlayストアにも該当するAPIがないか調べてみたのですが見つからず、
代わりにストアページのhtmlを取得してパースする方法が見つかりました。
Google Playにあるアプリのバージョンを取得したい – すなばいじり

これで解決かと思い、ストアページのhtmlを覗いてみたところ、
どうも記事の投稿時点(6年前)と今とでは形式が異なるようで、
パースする正規表現に関しては考え直す必要がありました。

手法
考え直してC#にて実装した方法が以下のやりかたです。

    /// <summary> ストア上のアプリバージョンを取得します。 </summary>
    public async Task GetStoreAppVersion(string appPackageId) {
        // 毎回HttpClientのインスタンスを作り直すのは良くないそうですがサンプルコードなので
        using var httpClient = new HttpClient(); 

        // ストアページのhtmlをhttpで取得してバージョンをパースする
        var playStoreUrl = $"https://play.google.com/store/apps/details?id={appPackageId}";

        // Getリクエストを投げて、結果のContentをstringで取得する処理(実装省略
        var content = await GetContent(httpClient, playStoreUrl);

        // バージョン部 (?\d+\.\d[\d\.]*): 数字1個以上 ドット1個 数字ないしドット0個以上
        // 一致例:<script class="ds:0" nonce="EssQcMMS9rrE4BPTbLD7PA">AF_initDataCallback( ...... ],[[[1.0.0]],[ ...... );</script>
        var match = Regex.Match(content, @"<script class=""ds:\d+"" nonce=""[^""]+"">AF_initDataCallback\(.+?\[\[\[""(?\d+\.\d[\d\.]*)""\]\].+?\);</script>");

        if (match.Success.Not()) return null;

        var ver = match.Groups["ver"].Value;
        return new Version(ver);
    }

この実装により、数字とドットからなる一般的な記法のバージョンをパース可能です。
※バージョンが特殊な記法の場合はバージョン部の正規表現を変える必要があります。
 また、バージョンがデバイスにより異なる場合も取得できません。
 一般向けの大手のアプリはデバイスにより異なる場合が多く取得できないので、
 残念ながら汎用性はないです。
※正規表現文字列は変数化しようかと思ったのですが、
 VSがいい感じに見やすく色分けしてくれたのでそのままにしました

取得例
例として先行事例での対象アプリ、Nintendo Switch Onlineから取得してみます。
引数appPackageIdに”com.nintendo.znca”を与えてGetStoreAppVersionを呼ぶと、
html内の以下の記述にマッチし、バージョン2.10.1が取得できます。

<script class=”ds:0″ nonce=”EssQcMMS9rrE4BPTbLD7PA”>AF_initDataCallback( …… ],[[["2.10.1"]],[ …… );</script>

ストアのページでバージョンを確認してみると実際に2.10.1と表示されているので、
正しくバージョンを取得できていることがわかります。
(この表示から取得するので、ここに”デバイスにより異なります”と記載されている場合は取得できません。)

おわりに
アプリの更新を促す処理の実装のために、
GooglePlayストアページのhtmlからアプリのバージョンをパースして取得する方法の
令和最新版(24/8/16現在)についての共有でした。

この方法はデバイスによりバージョンの異なるアプリは取得できませんが、
自社で開発するアプリならバージョンを統一できるので問題なく使用できます。

今後は工場向けのモバイルアプリも増えていくかと思いますので、
その開発の際にも役に立つ機能です。

ちなみに更新を促す機能を実装する手段としては他に、
Google Play CoreライブラリのIn-app updates機能を使用して
バージョンチェックから更新処理までの一連の流れを
ライブラリの機能で実装する方法もあるようでした。
おそらくそちらが主流だと思われたのですが、
以下の理由などにより泣く泣く不採用(バージョン取得の方を採用)としました。

・ライブラリの調査にかかる時間が不透明
・デバッグしながらの開発ができず開発コストが増える
(GooglePlayConsoleにリリースビルドしてアップロードしないとおそらくテストできないため)
・iPhoneアプリにて既にitunesAPIでバージョン取得・自前で比較・ストアに遷移まで行っており、
 バージョン取得の方法であれば実装の共通化が可能でその分開発コストが下がる

今の方法だとストアのhtmlの形式が変更される度に
バージョンをパースする部分をメンテしないといけないので、
あまり変更されないと、、、良いな、、、とGoogle本社の方角に向けてお祈りしております。

以上です。

KOM-MICSの紹介動画

インフォテックがコマツ様と協力して作成している「工場見える化システム KOM-MICS」に関する、ものづくり太郎さんの紹介動画がアップされました。
https://www.youtube.com/watch?v=PSxVDmUP5hw

インフォテックの作成するソフトは普段はあまり表に出ないため、このような紹介動画が作られるのは初めてです。

KOM-MICSは、工作機械や溶接機などから稼働データや加工データなどの各種データを収集し、分析することで工場の稼働状況を可視化し、最適化に向けた施策立案を支援します。
生産プロセスの見える化と最適化により、コマツグループの生産性向上に大きく貢献しています。もし工場の見える化や改善活動に興味がある方は、ぜひ動画をご覧いただければと思います。

また、インフォテックのKOM-MICSサイトにも情報を記載しております。
https://www.infortec.co.jp/lp_kom-mics.php

プログラミングの難易度

こんにちはmtjです。

プログラミングという物は行うことによって難易度がバラバラです。
同じ物を作る場合でも知識のある人であれば一瞬で終わり、無い人は1日かかってしまうという物もよくあります。
特にC++等メモリを理解していないと躓きやすい言語はその傾向が強いと思います。

そういった異常事態、異なる言語でもそれなりに対応できるように日々勉強していきたいです。

できればスレッド操作、メモリ操作等は全く気にせずに実装できるようになるといいですね

C# 値オブジェクトの基底クラス

.Net7 から追加された INumber<T> インターフェースを用いて、
値オブジェクトの基底クラスとなるような record クラスを作成しました。
INumber<T> 型を使用するジェネリッククラスとすることで、
int や double などを場合分けせずに記述することが可能となります。

プリミティブな値型と同じような感覚で扱え、
重要なドメインルールを持つクラスを簡単に作成できるようにすることが目的です。

/// <summary>値の基底クラス</summary>
public abstract record ValueBase<TValue, TSelf>
    where TValue : INumber<TValue>
    where TSelf : ValueBase<TValue, TSelf> {

    private static readonly Func<TValue, TSelf> CreateObject;

    /// <summary>値</summary>
    public TValue Value { get; }

    /// <summary>静的コンストラクタ</summary>
    static ValueBase() {
        var constructorInfo = typeof(TSelf).GetConstructor([typeof(TValue)])
                           ?? throw new NotSupportedException();
        CreateObject = v => (TSelf)constructorInfo.Invoke([v]);
    }

    /// <summary>コンストラクタ</summary>
    protected ValueBase(TValue value) {
        Value = value;
    }

    /// <summary>暗黙的型変換</summary>
    public static implicit operator TValue(ValueBase<TValue, TSelf> valueObject) {
        return valueObject.Value;
    }

    /// <summary>+オペレーター</summary>
    public static TSelf operator +(ValueBase<TValue, TSelf> lhs, ValueBase<TValue, TSelf> rhs) {
        return CreateObject(lhs.Value + rhs.Value);
    }

    /// <summary>ToString</summary>
    public sealed override string ToString() {
        return Value.ToString()!;
    }
}

静的コンストラクタでは、リフレクションを用いてTValue型を引数にとり
継承先のオブジェクトを生成する関数を作成しています。

この例では四則演算のうち加算のみ実装しています。
値オブジェクトから数値への暗黙変換を許可しています。
またrecord 型の継承先クラスではコンパイラにより ToString() が自動でオーバーライドされるので、
数値のみが出力されるように ToString() を sealed にしています。

使用する側は以下のようにします。

/// <summary>得点クラス</summary>
public sealed record Score : ValueBase<int, Score> {
    public Score(int value) : base(value) {}
}

// 使用例
Score score10 = new Score(10);
Score score20 = new Score(20);
Score total = score10 + score20;
Console.WriteLine(total);    // 30 が出力される

 


					

夏バテ

夏が始まってしまい暑くなって参りましたが、皆さんいかがお過ごしでしょうか。

自分は悲しきかな既に夏バテ気味でおります。
暑さで体力を奪われると体調を崩したり活動に支障を来したりで困るので、
早く涼しくなって欲しいものです。

皆さんは夏バテに負けずに元気に過ごされることを祈っております。

スイッチボットによる設備のレトロフィット

スイッチボットは、家庭の照明スイッチに貼り付けて、スマートホーム化に使用されるIoTデバイスです。
一般的にはリモコン操作や遠隔操作で照明や家電を制御できるようになります。

最近の案件で、工場の既存の生産設備にスイッチボットを取り付けて、遠隔操作を行う案件を行いました。
スイッチボット自体は低価格で手軽に導入でき、ソフト開発の費用も最初の1台分だけなので、複数台の既存設備に簡単安価にレトロフィットすることが可能です。

無人化の弱点

こんにちはmtjです

海外の人に電車の切符の取り方を聞かれた時に 明らかに在来線の切符売場では買えない範囲の切符だった時に無人機ではそういった状況には対応できないなと感じてしまいました。

京都の地下から有人のみどりの窓口が消えてしまってのでその時は新幹線の売り場まで案内しましたが
その人達はどんだけ頑張ってもあの場所で新幹線の切符は取れなかったんだろうなと思ってしました。

自動機は目的の物が売っている前提で買うなら優秀ですし 早いのですが
目的の物が無い物を買おうとする時がどうしても弱い

人であれば目的の物がなければ その場所を伝えてくれたり
そもそも近辺では売ってないという事を伝えられますが 自動機相手の場合は売ってない事自体も自力で調べないといけないので何も知らない状態で行動するには不向きだなと感じました。

それこそAIが発展しても 何を聞けばいいかわからないというような状態の人にはAIもかなり厳しいと感じます
AIも操作する人が質問等を行って初めて使うことができるので 順を追ってやりたい事を聞き出せる人にはなかなか敵わないだろうなと感じました。

C# using文でオブジェクト初期化子を使用すると警告される

C#でusing文やusing変数宣言(C#8.0~)のオブジェクトに対して、
オブジェクト初期化子を使用すると警告がでます。
以下の画像で、zipFile2の方はnewの部分が波線で警告されているのが分かります。

これはオブジェクト初期化子の処理中で例外が発生した場合に、
オブジェクトが正しく Dispose されないことが原因です。
Low-level なコードで表すと、オブジェクト初期化処理はtry文の外にあり、
この場合はコンストラクタで例外が発生した場合と同様に、
Dispose が呼ばれないことが確認できます。

オブジェクトの初期化に関しては、
一般的にオブジェクト初期化子を利用するのが良いと言われています。
ただし、using文ではその限りではないことが分かりました。

また IDisposable を継承するクラスに関しても、
プロパティ設定で例外が発生するような設計は避けるべきです。
ただ設計によっては record型の値オブジェクト等のコンストラクタでは、
例外を出すことも珍しくない為、usingのオブジェクト初期化子には注意が必要です。

MAUIにてFirebase使用にあたり困ったこと ~VSのビルドアクションの選択肢にGoogleServicesJsonが出て来ない~

先日、MAUIのAndroidアプリにてプッシュ通知を実現するため、
Firebase Cloud Messagingを用いる方法を調査していました。
その際に詰まったことがあったので共有します。

Firebaseから取得した設定ファイル google-services.json をプロジェクトに含める必要があるのですが、
含めた後にプロパティからビルドアクションをGoogleServicesJsonに変更しないと
Firebaseの処理が動作しません。

しかしビルドアクションの選択肢にGoogleServicesJsonが出てきませんでした。

これは定番の困りごとのようで、調べてみると解決策がいくつか出てきたのですが
自分の環境ではどれも上手く行かず、ようやく上手く行ったのは以下の手順による方法でした。

1. nugetで以下のパッケージをインストール
※1つずつ試してはないので不要なもの含まれている可能性有

Xamarin.Firebase.Common
Xamarin.Firebase.Config
Xamarin.Firebase.Iid
Xamarin.GooglePlayServices.Base
Xamarin.GooglePlayServices.Basement
Xamarin.GooglePlayServices.Tasks

2. .csprojに以下を直接記述して、対象のパッケージの.targetファイルをインポート


<Import Project="C:\Users\<my user>\;.nuget\packages\xamarin.googleplayservices.basement\118.3.0.1\buildTransitive\net7.0-android33.0\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('C:\Users\<my user>\.nuget\packages\xamarin.googleplayservices.basement\118.3.0.1\buildTransitive\net7.0-android33.0\Xamarin.GooglePlayServices.Basement.targets')" />

3. 2の記述にてnugetフォルダパスが個人のローカルパスになっているため、マクロに置き換え
※個人プロジェクトなら不要です

C:\Users\<my user>\.nuget\packages\ → $(NuGetPackageRoot)


<Import Project="$(NuGetPackageRoot)xamarin.googleplayservices.basement\118.3.0.1\buildTransitive\net7.0-android33.0\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('$(NuGetPackageRoot)xamarin.googleplayservices.basement\118.3.0.1\buildTransitive\net7.0-android33.0\Xamarin.GooglePlayServices.Basement.targets')" />

これで無事ビルドアクションの選択肢にGoogleServicesJsonが出て来て、
Firebaseのプッシュ通知を動作させることができました。

以下のページにより解決に辿り着けました。感謝します。
xamarin – Can't set Build Action to GoogleServiceJson – Stack Overflow