フリーランス 技術調査ブログ

フリーランス/エンジニア Ruby Python Nodejs Vuejs React Dockerなどの調査技術調査の備忘録

Android Studioでemulatorが起動しなくなった

はじめに

  • 2週間ぶりにAndroid Studioで開発をしようとしたらemulatorを起動したところ、下記のエラーが発生したので、調査してみて。

エラーの内容

WARNING | unexpected system image feature string, emulator might not function correctly, please try updating the emulator. VK_VERSION_1_0 check failed: vkCreateInstance not found VK_VERSION_1_0 check failed: vkEnumerateInstanceExtensionProperties not found VK_VERSION_1_0 check failed: vkEnumerateInstanceLayerProperties not found createOrGetGlobalVkEmulation: Warning: Vulkan 1.0 APIs missing from instance INFO | Android emulator version 31.2.8.0 (build_id 8143646) (CL:N/A)

下記の方法を試したけどダメだったこと

対応方法

  • C:\Users\user\.android\advancedFeatures.iniのファイルを作成して下記の記述を追記するだけで起動することができました。
Vulkan = off
GLDirectMen = on

FlutterでRiverpodを使用してみた

Riverpodとは

状態管理ライブラリです。

Providerの種類

種類 説明
Provider 定数
StateProvider 変数・標準
ScopedProvider 出力を指定する
StateNotifierProvider メソッド付き
FutureProvider Future版
StreamProvider Stream版
ChangeNotifierProvider ChangeNotifierを使う

Consumerの種類

種類 説明
Consumer Widgetに埋め込む
ConsumerWidget StatelessWidgetのRiverpod版
ConsumerStatefulWidget StatefulWidgetのRiverpod版
簡単に実装できるが、パフォーマンスはあまりよくない

表示方法

表示方法 説明
watch 状態変更でWidgetを更新
read 状態変更でWidgetを更新しない
select データ内の特定の値が変更したときのみWidgetを更新する

ConsumerWidget

  • ステータスが更新されるたびに画面全体がリロードされてしまう。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'provider.dart';

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  MyHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print("MyHomePage rebuild");
    return Scaffold(
      appBar: AppBar(
        title: Text(ref.watch(titleProvider)),
      ),
      body: Center(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              ref.watch(messageProvider)
            ),
            Text(
              // ref.watch(countProvider.state).state.toString()
              // ref.watch(countProvider).toString()
              // ref.watch(countProvider.notifier).state.toString()
              ref.watch(countProvider.state).state.toString(),
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.watch(countProvider.state).state ++,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
import 'package:flutter_riverpod/flutter_riverpod.dart';

final titleProvider = Provider<String>((ref) {
  return 'Riverpod Demo Home Page';
});

final messageProvider = Provider<String>((ref) => 'テストテストテストメッセージが入ります');

final countProvider = StateProvider<int>((ref) => 1);

Consumer

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'provider.dart';

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    print("MyHomePage rebuild");
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Consumer(
          builder: (BuildContext context, WidgetRef ref, Widget? child) => Text(
            ref.watch(titleProvider)
          ),
        ),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Consumer(
              builder: (context, ref, child) => Text(
                  ref.watch(messageProvider)
              ),
            ),
            Consumer(
              builder: (BuildContext context, WidgetRef ref, Widget? child) {
                return Text(
                  // ref.watch(countProvider.state).state.toString()
                  // ref.watch(countProvider).toString()
                  // ref.watch(countProvider.notifier).state.toString()
                  ref.watch(countProvider.state).state.toString(),
                  style: Theme.of(context).textTheme.headline4,
                );
              }),
          ],
        ),
      ),
      floatingActionButton: Consumer(
        builder: (context, ref, child) {
          print('button rebuild');
          return FloatingActionButton(
            onPressed: () => ref.read(countProvider.state).state ++,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          );
        },
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

pythonのbeautifulsoup4を利用してサイト内にあるすべてのリンクを抽出してみる

はじめに

  • 運用しているサイトでバックアップファイルが多く残っているサイトで、現時点でトップページから正しくリンクが張られているページを調査するため、手動でチェックするのは、しんどいのでプログラムで調査できないか、サンプルのプログラムを作成してみました。

利用したパッケージ

beautifulsoup4
requests

サンプルコード

from html.parser import HTMLParser
from bs4 import BeautifulSoup
import requests

baseurl = "<調査したいURL>";
urlList = []
ok_urlList = []

def searchLink(url):
    global urlList
    global ok_urlList
    if url == "":
        response = requests.get(f"{baseurl}index.php")
    else:
        response = requests.get(f"{url}")

    html = BeautifulSoup(response.text, 'html.parser')
    for link in html.findAll("a"):
        href = link.get('href')
        if href is None:
            pass
        else:
    # hrefがないAタグや、index.htmlがある場合は省く
            if ("http" not in href and "index.html" not in href):
                href = href.replace('../', '')
                href = href.replace('./', '')
                href = href.replace('//', '')
     # 拡張子がphpとhtmlファイルのURLだけ取得する
                if (".php" in href or ".html" in href ):
                    if href[-1] == "/":
                        urlList.append(f"{baseurl}{href}index.php")
                    else:
                        if href[0] == "/":
                            urlList.append(f"{baseurl}{href[1:]}")
                        else:
                            urlList.append(f"{baseurl}{href}")

  # 配列に格納されている値をユニークにする
    urlList = list(dict.fromkeys(urlList))
 # チェック済みのURLは省く
    result = list(set(urlList) - set(ok_urlList))
    if result:
        ok_urlList.append(f"{result[0]}")
        # まだチェックしていないURLを指定する
        searchLink(result[0])
    else:
   # チェックするURLがなくなったら処理を終了する
        return

searchLink("")
print(ok_urlList)

Flutterでsqlliteを利用してタスク管理を実装する

はじめに

  • 作成途中のプログラムになりますが、Flutterでsqlliteを利用してタスクの登録と削除を行えるアプリを開発する。 ※変更処理は途中の状態です。

インストールのパッケージ

  • sqfliteとpath_providerをインストールする
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  sqflite: ^2.0.2
  path_provider: ^2.0.8

コード

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

import 'next_page.dart';


void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final textController = TextEditingController();
  int? selectedId;
  int tabIndex = 1;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home:Scaffold(
          appBar: AppBar(
              title: Text("タスク管理")
          ),
          body: Center(
            child: FutureBuilder<List<Task>>(
                future: DatabaseHelper.instance.getTasks(),
                builder: (BuildContext context,
                    AsyncSnapshot<List<Task>> snapshot) {
                  if (!snapshot.hasData) {
                    return Center(child: Text('Loading...'));
                  }
                  return snapshot.data!.isEmpty
                      ? Center(child: Text('No Tasks.'))
                      : ListView(
                    children: snapshot.data!.map((task) {
                      return Center(
                          child: Card(
                            color: selectedId == task.id
                                ? Colors.white70
                                : Colors.white,
                            child: ListTile(
                              title: Text(task.name),
                              onTap: () {
                                if (selectedId == null) {
                                  textController.text = task.name;
                                  selectedId = task.id;
                                } else {
                                  textController.text = '';
                                  selectedId = null;
                                }
                              },
                              onLongPress: () {
                                setState(() {
                                  DatabaseHelper.instance.remove(task.id!);
                                });
                              },
                            ),
                          )
                      );
                    }).toList(),
                  );
                }),
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.save),
            onPressed: ()async {
              selectedId != null
                  ? await DatabaseHelper.instance.update(
                Task(id: selectedId, name: textController.text),
              )
                  : await DatabaseHelper.instance.add(
                Task(name: textController.text),
              );
              setState(() {
                textController.clear();
                selectedId = null;
              });
            },
          ),
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabIndex,
            items: [
              BottomNavigationBarItem(
                label: 'Home',
                icon: Icon(Icons.home),
              ),
              BottomNavigationBarItem(
                label: 'AC Unit',
                icon: Icon(Icons.ac_unit),
              ),
            ],
            onTap: (int index) {
                setState((){
                  tabIndex = index;
                });
                showDialog(
                  context: context,
                  builder: (_) {
                    return AlertDialog(
                      title: Text("タイトル"),
                      content: TextField(
                        controller: textController,
                      ),
                      actions: <Widget>[
                        // ボタン領域
                        ElevatedButton(
                          child: Text("Cancel"),
                          onPressed: () => Navigator.pop(context),
                        ),
                        ElevatedButton(
                          child: Text("OK"),
                          onPressed: () async {
                            selectedId != null
                                ? await DatabaseHelper.instance.update(
                              Task(id: selectedId, name: textController.text),
                            )
                                : await DatabaseHelper.instance.add(
                              Task(name: textController.text),
                            );
                            setState(() {
                              textController.clear();
                              selectedId = null;
                            });
                            //Navigator.push(context, MaterialPageRoute(builder: (context) => NextPage()))
                          },
                        ),
                      ],
                    );
                  },
                );
                //Navigator.push(context, MaterialPageRoute(builder: (context) => NextPage()));
            },
          ),
        )
    );
  }
}

class Task {
  final int? id;
  final String name;

  Task({this.id, required this.name});

  factory Task.fromMap(Map<String, dynamic>json) => new Task(
      id: json['id'],
      name: json['name']
  );

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name
    };
  }
}

class DatabaseHelper {
  DatabaseHelper._privateConstructor();
  static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

  static Database? _database;
  Future<Database> get database async => _database ??= await _initDatabase();

  Future<Database> _initDatabase() async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String path = join(documentsDirectory.path, 'tasks.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE tasks(
          id INTEGER PRIMARY KEY,
          name TEXT
      )
      ''');
  }

  Future<List<Task>> getTasks() async {
    Database db = await instance.database;
    var tasks = await db.query('tasks', orderBy: 'name');
    List<Task> taskList = tasks.isNotEmpty
        ? tasks.map((c) => Task.fromMap(c)).toList()
        : [];
    return taskList;
  }
  Future<int> update(Task task) async {
    Database db = await instance.database;
    return await db.update('tasks', task.toMap(),
        where: "id = ?", whereArgs: [task.id]);
  }

  Future<int> add(Task task) async {
    Database db = await instance.database;
    return await db.insert('tasks', task.toMap());
  }

  Future<int> remove(int id) async {
    Database db = await instance.database;
    return await db.delete('tasks', where: 'id = ?', whereArgs: [id]);
  }
}

作成した画面

f:id:PX-WING:20220121201314p:plain

flutterでtimerを利用して自動で画面画面遷移する

サンプルコード

import 'dart:async';

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: const Scaffold(
        body: Center(
          child: FirstScreen(),
        ),
      ),
    );
  }
}

/// Releaxing screen that stays visible for 3 seconds
class FirstScreen extends StatefulWidget {
  const FirstScreen({Key? key}) : super(key: key);

  @override
  _FirstScreenState createState() => _FirstScreenState();
}

class _FirstScreenState extends State<FirstScreen> {
  //Declare a timer
  Timer? timer;


  @override
  void initState() {
    super.initState();

    timer = Timer(
      const Duration(seconds: 3),
      () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => const NextScreen(),
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    super.dispose();
    timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return const Image(
      image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
    );
  }
}

class NextScreen extends StatelessWidget {
  const NextScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text("Next Screen"),
      ),
    );
  }
}

blog.logrocket.com

python/BeautifulSoupを利用して複数HTMLファイルを一括置換する

はじめに

  • 直近で某サイトのヘッダーやサイドナビが各ページに記述されており、ヘッダーやサイドナビが変更が発生するたびに対象ページをgrep 置換している運用しているサイトがあり、ヘッダーやサイドナビを共通化してほしいというお題を頂いたので、手作業で修正するよりプログラムで一括できないか調査してみた。

HTMLタグを置換するプログラム (その①)

  • BeautifulSoupを利用して置換対象のタグを取得し、取得した箇所の文字列を置換処理で対応をしようとしたところ、BeautifulSoupでHTMLを読み込むとパーサーがHTMLのフォーマットを勝手に置換してしまい、修正する必要がない箇所も修正してしまっていました。 またPHPソースコードエスケープしていまい、今回の作業には向かなかったので、違う方法を検討する。
import os
from html.parser import HTMLParser
from bs4 import BeautifulSoup

ROOT_PATH = '/code'
def find_all_files(directory):
    for cur_dir, dirs, files in os.walk(directory):
        for file in files:
            yield os.path.join(cur_dir, file)

## 共通ヘッダー変換
def header_change(): 
    for file in find_all_files(ROOT_PATH):
        if ('.htm' in file or '.php' in file):
            f = open(file, 'r+', encoding='UTF-8', errors='ignore')
            data = f.read()
            soup = BeautifulSoup(data, 'html.parser')
            new_tag = soup.new_tag('b')
            new_tag.string = "一時的に挿入する文字列です。その後置換する文字列に切り替わります。"
            nav = soup.find("nav")
            if nav is None:
                print(f'ヘッダーがないページ,{file}')
            else:                 
                soup.nav.insert_before(new_tag)
                soup.nav.extract()
                f.seek(0)
                new_html = soup.prettify(formatter=None)
                if ('en/' in file):
                    print(f'英語ページ,{file}')
                    f.write(new_html.replace('<b>一時的に挿入する文字列です。その後置換する文字列に切り替わります。</b>', "<?php include($_SERVER['DOCUMENT_ROOT'].'/common/html/global_navi_en.html'); ?>"))
                    f.write(data.replace(str(nav), "<?php include($_SERVER['DOCUMENT_ROOT'].'/common/html/global_navi_en.html'); ?>"))
                else:
                    print(f'日本語ページ,{file}')
                    f.write(new_html.replace('<b>一時的に挿入する文字列です。その後置換する文字列に切り替わります。</b>', "<?php include($_SERVER['DOCUMENT_ROOT'].'/common/html/global_navi.html'); ?>"))
                f.truncate()
                f.close()    
            f.close()

header_change()

HTMLタグを置換するプログラム (その②)

  • 下記のプログラムは対象ディレクトにあるHTMLファイルおよびPHPファイルの中に記述されている特定のタグの開始位置と終了位置を取得し、取得した位置の範囲あるタグを共通ヘッダーを読み込む処理のコードに置換する処理となっております。 下記の記述にすることで特に問題なく置換することができました。
import os

ROOT_PATH = '/code'

def find_all_files(directory):
    for cur_dir, dirs, files in os.walk(directory):
        for file in files:
            yield os.path.join(cur_dir, file)


## 共通ヘッダー変換
def header_change(): 
    for file in find_all_files(ROOT_PATH):
        if ('.htm' in file or '.php' in file):
            f = open(file, 'r+', encoding='UTF-8', errors='ignore')
            data = f.read()
            start = data.find('<nav id="globalNav">', 0)
            end = data.find('</nav>', start)
            if start == -1:
                print(f'ヘッダーがないページ,{file}')
            else:
                if ('common/html' not in file):
                    f.seek(0)
                    if ('en/' in file):
                        print(f'Englishページ,{file}')
                        f.write(data.replace(data[start:end+6], "<?php include($_SERVER['DOCUMENT_ROOT'].'/hoge_navi_en.html'); ?>"))
                    else:
                        print(f'日本語ページ,{file}')
                        f.write(data.replace(data[start:end+6], "<?php include($_SERVER['DOCUMENT_ROOT'].'/hoge_navi.html'); ?>"))
                    f.truncate()
            f.close()

header_change()

上記の件を動かしていた環境

docker-compose.yml

version: '3'
services:
  python:
    build: .
    volumes:
      - ../htdocs:/code
    tty: true

Dockerfile

FROM python:3.10-alpine
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt

requirements.txt

beautifulsoup4

FlutterとFirebaseの連携手順のメモ

FirebaseとFlutterの設定

  • Firebaseにプロジェクトを作成する

  • google-service.jsonファイルをダウンロードして下記にフォルダに設置する

\<Your Project Root>\android\app\google-services.json
  • /<Your Project Root>/android/build.gradleファイルにclasspath 'com.google.gms:google-services:4.3.10'に追加する
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.10'
    }
  • android/app/build.gradleファイルに下記の記述を追記する
apply plugin: 'com.google.gms.google-services'


dependencies {
    ・・・・
    implementation platform('com.google.firebase:firebase-bom:29.0.3')
    implementation 'com.google.firebase:firebase-analytics-ktx'
}

エラーが発生したときの対処

  • 上記の設定をしてもエラーが発生する場合、下記の対応して解決したので備忘録としてメモをしておく。

  • android/app/build.gradleファイルの下記の箇所を修正する

From

        minSdkVersion flutter.minSdkVersion
        targetSdkVersion flutter.targetSdkVersion

To

   minSdkVersion 19
   targetSdkVersion 30
  • android/build.gradleファイルは下記のように変更する From
    //ext.kotlin_version = '1.3.50'
    ext.kotlin_version = '1.6.10'

Flutterのパッケージインストール

  • pubspec.yamlファイルにcloud_firestorefirebase_coreの記述を追加する
dependencies:
  flutter:
   sdk: flutter
(省略)
   cloud_firestore: ^3.1.5
   firebase_core: ^1.10.6
  • android/app/build.gradleファイルにmultiDexEnabledの記述を追加する
    defaultConfig {
(省略)
        multiDexEnabled true
    }

Flutterの実装

  • lib/main.dartファイルのmain関数にFirebaseの初期化とWidgetsFlutterBinding.ensureInitializedを追加する
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}
  • 参考URL

stackoverflow.com

Firestoreへの登録処理

  • 下記のクラスを呼び出すとFirebasestoreにデータが書き込まれる。
import 'package:cloud_firestore/cloud_firestore.dart';

class Firestore {
  static FirebaseFirestore _firebaseFirestore = FirebaseFirestore.instance;
  
  static Future<void> addUser() async {
    try{
      final userRef = await _firebaseFirestore.collection('user').add({
        'name': '山田 太郎',
        'imagePath': 'https://item-shopping.c.yimg.jp/i/l/project1-6_4530956470801',
      });
      print('アカウント作成に成功しました。$userRef');
    } catch (e){
      print('アカウント作成に失敗しました  $e');
    }
  }
}

f:id:PX-WING:20211231193043p:plain