MacからリモートデスクトップでWindows10に接続するときにIPアドレスを指定していたのだが、固定IPにしていないと、IPアドレスが変わって面倒くさい。

ググると、udp 5353ポートを開けるだけどよさそうと情報があったが、手順を忘れてしまいそうなのでメモしておく。

接続先Windows10 PCの作業

Windows ファイアーウォールを起動して、詳細設定

mac_rdp01

左ペインで受信の規則、右で新しい規則を選択

mac_rdp02

ダイアログが立ち上がるので、ポートを選択

mac_rdp03

UDPの5353ポート

MultiMulticast DNS(mDNS)

mac_rdp04

接続を許可して、次へ次へ

mac_rdp05

名称をmDNSとでもしておく。

mac_rdp06

設定できた

mac_rdp07

Mac側で確認

[host名].local で pingが通ることを確認。

mac_rdp10

リモートデスクトップの設定も、IPアドレスから、[host名].local に変更

mac_rdp11

これは地味に便利!

Flutter : sqliteの利用

Flutter + Firebase でアプリ作成のためのパーツあつめ。

1.ここまでに確認したこと

  1. Flutter+Firebase環境設定
  2. 画面遷移
  3. Dialog
  4. Httpクライアント
  5. AndroidX対応
  6. 画像の選択
  7. 画像の切り抜き
  8. HTTP POSTと結果JSONの処理

2.今回の確認

sqliteの利用

sqflite パッケージの利用

sqfliteのページのチュートリアルを見ながらコードを記述し、動作確認

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DbTest {
  void firstDbTest() async {
    // OSなど環境別のDBディレクトリパスを取得する
    var dbPath = await getDatabasesPath();
    // DBファイルパス
    dbPath = join(dbPath, 'demo.db');
    print("DB Path $dbPath");

    var helper = MemoProvider();
    Database db = await helper.open(dbPath);

    print("DB: ${db.toString()}");

    var list = await helper.getAll();
    if (list.length < 10) {
      var newMemo = Memo();
      newMemo.memo = "Memo ${DateTime.now()}";
      helper.insert(newMemo);
    }

    for(Memo memo in list) {
      print("MEMO : ${memo.toMap().toString()}");
    }
  }
}

class MemoMeta {
  static String tableName = "Memo";
  static String columnId = "_id";
  static String columnMemo = "memo";

  static List<String> columns() {
    return [columnId, columnMemo];
  }
}

class Memo {
  int id;
  String memo;

  Map<String, dynamic> toMap() {
    var map = <String, dynamic> {
      MemoMeta.columnMemo: memo
    };
    if (id != null) {
      map[MemoMeta.columnId] = id;
    }
    return map;
  }

  Memo();

  Memo.fromMap(Map<String, dynamic> map) {
    id = map[MemoMeta.columnId];
    memo = map[MemoMeta.columnMemo];
  }
}

class MemoProvider {
  Database db;

  Future<Database> open(String path) async {
    var ddl = """
create table ${MemoMeta.tableName} (
  ${MemoMeta.columnId} integer primary key autoincrement,
  ${MemoMeta.columnMemo} text not null )
""";
    print("DDL: $ddl");

    db = await openDatabase(path, version: 1,
      onCreate: (Database db, int version) async {
        await db.execute(ddl);
      }
    );
    return db;
  }

  Future<Memo> insert(Memo memo) async {
    memo.id = await db.insert(MemoMeta.tableName, memo.toMap());
    return memo;
  }

  Future<List<Memo>> getAll() async {
    List<Map> maps = await db.query(
        MemoMeta.tableName,
        columns : MemoMeta.columns());
    var list = new List<Memo>();
    for(Map map in maps) {
      list.add(Memo.fromMap(map));
    }
    return list;
  }

  Future<Memo> findById(int id) async {
    List<Map> maps = await db.query(MemoMeta.tableName,
      columns: MemoMeta.columns(),
      where: '${MemoMeta.columnId} = ?',
      whereArgs: [id]);
    if (maps.length > 0) {
      return Memo.fromMap(maps.first);
    }
    return null;
  }

  Future<int> delete(int id) async {
    return await db.delete(
        MemoMeta.tableName, where: '${MemoMeta.columnId} = ?', whereArgs: [id]);
  }

  Future<int> update(Memo memo) async {
    return await db.update(MemoMeta.tableName, memo.toMap(),
      where: '${MemoMeta.columnId} = ?', whereArgs: [memo.id]);
  }

  Future close() async => db.close();

}

ログに出力された。

I/flutter ( 3116): DB Path /data/user/0/info.typea.favo_phrase_trial/databases/demo.db
I/flutter ( 3116): DDL: create table Memo (
I/flutter ( 3116):   _id integer primary key autoincrement,
I/flutter ( 3116):   memo text not null )
I/flutter ( 3116): DB: 2 /data/user/0/info.typea.favo_phrase_trial/databases/demo.db
V/AudioManager( 3116): playSoundEffect   effectType: 0
V/AudioManager( 3116): querySoundEffectsEnabled...
I/flutter ( 3116): MEMO : {memo: Memo 2019-12-09 21:18:12.816597, _id: 1}
I/flutter ( 3116): MEMO : {memo: Memo 2019-12-09 21:18:17.761803, _id: 2}

Flutter : HTTP POSTと結果JSONの処理

Flutter + Firebase でアプリ作成のためのパーツあつめ。

1.ここまでに確認したこと

  1. Flutter+Firebase環境設定
  2. 画面遷移
  3. Dialog
  4. Httpクライアント
  5. AndroidX対応
  6. 画像の選択
  7. 画像の切り抜き

2.今回確認すること

  1. Image Pickボタン押下:画像を選択し、画像を画面に表示
  2. OCRボタン押下:dioパッケージ を使用して、HTTP POST で、OCRサービス(Google Cloud VIsioin API)に画像をアップロードし、結果をJOSNで得る
  3. 結果のJSONを処理してテキストに表示

3.ソース

3.1 pubspwc.yaml

dependencies:
   image_picker: ^0.6.1+8
   dio: ^3.0.3

3.2 テーマに該当する部分のみ抜粋。

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:dio/dio.dart';

class SecondRoute extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SecondRoute();
  }
}
class _SecondRoute extends State<SecondRoute>{
  Image _defaultImage = Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg');
  File _selectedFile;
  final _textController = TextEditingController();

  @override
  void initState() {
    super.initState();
//    _textController.addListener(_ocrText);
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  Future _handleImagePick(BuildContext context) async {
    var imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
    setState(() {
      _selectedFile = imageFile;
    });
  }

  Future _handleOcr(BuildContext context) async {
    var dio = new Dio();
    var formData = new FormData.fromMap({
      "fileName": "ocrfile",
      "file": await MultipartFile.fromFile(_selectedFile.path ,filename: "selected_file")
    });
    var result = dio.post(
        "https://{google cloud vision api を実装したWebサービスアプリのURL}",
        options: Options(responseType:  ResponseType.json),
        data: formData);

    result.then((Response response){
      print("<status> ${response.statusCode}");
      response.headers.forEach((k, v){
        print("<header> $k:$v");
      });
      var json = response.data;
      print(json['text']);
      setState(() {
        _textController.text = json['text'];
      });
    });
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints viewportConstraints) {
          return SingleChildScrollView(
            child: ConstrainedBox(
              constraints: BoxConstraints(
                minHeight: viewportConstraints.maxHeight,
              ),
              child: IntrinsicHeight(
                child: Column(
                  children: <Widget>[
                    Container(
                      // A fixed-height child.
                      color: const Color.fromARGB(255, 255, 255, 255),
                      height: 120.0,
                    ),
                    Expanded(
                      // A flexible child that will grow to fit the viewport but
                      // still be at least as big as necessary to fit its contents.
                      child: Container(
                        color: const Color(0xffffffff),
                        height: 240.0,
                        constraints: BoxConstraints(
                            minWidth: viewportConstraints.maxWidth,
                            minHeight: double.infinity
                        ),
                        child: Column(
                          children: <Widget>[
                            RaisedButton(
                              onPressed: () {
                                _handleImagePick(context);
                              },
                              child: Text('Image Pick'),
                            ),
                            RaisedButton(
                              onPressed: (){
                                _handleOcr(context);
                              },
                              child: Text('OCR'),
                            ),
                            (_selectedFile == null)?_defaultImage:Image.file(_selectedFile),
                            TextField(
                                controller: _textController,
                                minLines: 6,
                                maxLines: 15,
                                decoration: InputDecoration(
                                  border: OutlineInputBorder(),
                                ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }

}

4.実行

Image Pick で画像をロード

flutter_ocr01

OCRボタンで、画像をサービスに送信して、結果のJSONをテキストフィールドに表示

flutter_ocr02