端末のローカルファイルにアクセスすると「DirectoryNotFoundException」が発生。

Android では問題ないのに、iOSのみエラーが発生しました。

DirectoryNotFoundException: Could not find a part of the path "/var/mobile/Containers/Data/Application/E13DBFFC-F7F4-4CB2-BBDC-A5C9F44XXXXX/Documents/gdrive/Sample/images/photo0000-4165.jpg".
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0 
 ...

アクセスするファイルは存在しているのは間違いないのに、上記のエラー

っで、よくよく確認すると、フォルダー名が「Image」となっており、アクセス時は、「image」だった事が原因でした。

ファイル名も同じかな…

Unity ver2020.3.38f1 Personal

GoogleCredential credential = GoogleCredential.FromJson(jsonText).CreateScoped(
                DriveService.Scope.DriveReadonly
            	// ,DriveService.Scope.Drive
);

アカウント情報を利用して GoogleDrive にアクセスすると、Android だと以下のエラーとなります。

2023/09/29 21:44:05.541 4080 4294 Info Unity error:Error deserializing JSON credential data.

実際のエラーは、以下のエラーでした。対応策は一番最後を参照して下さい。

2023/09/29 21:44:05.538 4080 4294 Info Unity Error Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type Google.Apis.Auth.OAuth2.JsonCredentialParameters. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'type', line 2, position 7.
2023/09/29 21:44:05.538 4080 4294 Info Unity   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract objectContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 21:44:05.538 4080 4294 Info Unity   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerCo
2023/09/29 21:44:05.540 4080 4294 Info Unity finally

簡単に言うと、コンストラクターが見つからなかったようです。

上記のソースは、Unity のエミュレータでは動作しているので認証情報のJSONに間違いはないです。

Android 特有のエラーと考えられます。

 

ソースを clone して確認すると、以下のソースでエラーがあるのが確認できます。

https://github.com/googleapis/google-api-dotnet-client

./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs

google-api-dotnet-client % grep -r "Error deserializing JSON credential data" .                 
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
falco@M1 google-api-dotnet-client % 

以下のコードでエラーが発生していることが考えられます。

JsonCredentialParameters credentialParameters;
try
{   
    credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(json);
}       
catch (Exception e)
{
    throw new InvalidOperationException("Error deserializing JSON credential data.", e);
}
return CreateDefaultCredentialFromParameters(credentialParameters);

ネット検索すると以下のコードを追加しているようなので、追加 (Assets/link.xml)

https://forum.unity.com/threads/error-deserializing-json-credential-data-google-api.1367799/

<linker>
    <assembly fullname="Google.Apis.Auth">
        <type fullname="Google.Apis.Auth.OAuth2.JsonCredentialParameters" preserve="all" />
    </assembly>
</linker>

これで実行すると、次に以下のエラーが発生

2023/09/29 22:21:09.984 11023 11057 Info Unity Error System.NotSupportedException: System.Reflection.Emit.DynamicMethod::.ctor
2023/09/29 22:21:09.984 11023 11057 Info Unity   at System.Reflection.Emit.DynamicMethod..ctor (System.String name, System.Type returnType, System.Type[] parameterTypes, System.Type owner, System.Boolean skipVisibility) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Utilities.DynamicReflectionDelegateFactory.CreateDynamicMethod (System.String name, System.Type returnType, System.Type[] parameterTypes, System.Type owner) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Utilities.DynamicReflectionDelegateFactory.CreateDefaultConstructor[T] (System.Type type) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Serialization.DefaultContractResolver.InitializeContract (Newtonsoft.Json.Serialization.JsonContract contract) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateObjectContract (System.Type objectType) [0x00000] in <00000000000000000000000000

エラーの原因は以下の模様

Error System.NotSupportedException: System.Reflection.Emit.DynamicMethod::.ctor

再度ネット検索すると、以下がヒット

https://forum.unity.com/threads/notsupportedexception-with-log-detail.543644/

System.Reflection.Emit is not supported with IL2CPP. You will need to use a JSON library that does not require use of it to work with Hololens. I believe that Newtonsoft JSON does have a mode that works with AOT runtimes like IL2CPP.
System.Reflection.Emit は IL2CPP ではサポートされていません。 Hololens と連携するには、JSON ライブラリを使用する必要がありません。 Newtonsoft JSON には、IL2CPP のような AOT ランタイムで動作するモードがあると思います。

結局のところ、Scripting Backend を IL2CPP -> Mono にすることで、正常に動作する事が確認出来ました。

Thanks Unity forum.

これで解決と思いきや、iOS  でも確認しようと設定を切り替えると、iOS は、IL2CPP 固定となり振り出しに戻る。

「 Newtonsoft JSON には、IL2CPP のような AOT ランタイムで動作するモードがあると思います。」

との事なので、結局のところ IL2CPP を使う事で、ストリッピング機能によりリフレクションで関節的にコールしているメソッド等が削除される事が原因のようなので、これを抑止する事にします。

Newtonsoft.Json には、IL2CPP版がありこちらをインストールします。

Newtonsoft.Json-for-Unity

Assets 配下の Link.xml に以下を記述することで、Android、iOS で動作します。

<linker>
    <assembly fullname="Google.Apis.Auth" preserve="all" />
    <assembly fullname="Google.Apis.Drive.v3" preserve="all" />
    <assembly fullname="Newtonsoft.Json" preserve="all" />
</linker>

以下、抜粋のGoogle Drive にアクセスするソースです。

void Start()
{
    ListFilesRecursively("folderId");
}

private void InitGooglDrive()
{
    String jsonText = Resources.Load<TextAsset>("GoogleAuth/service_account").text;
    Debug.Log($"json:{jsonText}");

    JsonCredentialParameters credentialParameters;

    try {
        credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(jsonText);
    } catch (Exception e) {
        Debug.Log($"Error {e}");
    } finally {
        Debug.Log($"finally");
    }

    // OAuth 2.0認証をセットアップ
    Debug.Log("Call FromStream");
    GoogleCredential credential = GoogleCredential.FromJson(jsonText).CreateScoped(
        DriveService.Scope.DriveReadonly
    );

    // Drive APIクライアントを作成
    service = new DriveService(new BaseClientService.Initializer() {
        HttpClientInitializer = credential,
        ApplicationName = "Google shared folder viewer"
    });

}

private async void ListFilesRecursively(string folderId)
{
    InitGooglDrive();
    Debug.Log("folder:" + folderId);
    FilesResource.ListRequest listRequest = service.Files.List();
    listRequest.Q = $"'{folderId}' in parents";
    listRequest.Fields = "nextPageToken, files(id, name, mimeType, modifiedTime, md5Checksum)";

    do {
	var files = listRequest.Execute().Files;
	if (files == null || files.Count == 0) {
	    Debug.Log("No files found in folder: " + folderId);
	    continue;
	}

	foreach (var file in files) {
	    Debug.Log($"File Name: {file.Name}, ID: {file.Id}, MIME Type: {file.MimeType}, createdTime:{file.CreatedTime}");
   	    // フォルダの場合、再帰的にその中身を表示
 	    if (file.MimeType == "application/vnd.google-apps.folder") {
		ListFilesRecursively(file.Id);
  	    }
	 }
         if(listRequest.PageToken != null) {
             // next page token があればスリープ
             await Task.Delay(200);
         }
     } while (!string.IsNullOrEmpty(listRequest.PageToken));
}

 

Flutter で Google drive の createdTime, modifiedTime を取得するには?

 

$fields を明示しないと取得できなかった。

Future<void> _test(String folderId) async {
  final jsonString = await rootBundle.loadString(
      'assets/google_sheet_auth.json');
  final jsonData = json.decode(jsonString);
  final credentials = ServiceAccountCredentials.fromJson(jsonData);
  final scopes = [
    drive.DriveApi.driveReadonlyScope,
    // drive.DriveApi.driveMetadataReadonlyScope,
    // SheetsApi.spreadsheetsReadonlyScope,
  ];
  final client = await clientViaServiceAccount(credentials, scopes);
  var driveApi = drive.DriveApi(client);

  drive.FileList files = await driveApi.files.list(
    q: "'$folderId' in parents",
    $fields: 'files(id, name, mimeType, createdTime, modifiedTime)',
  );
  for (drive.File file in files.files ?? []) {
    log.d('${file.name} createdTime: ${file.createdTime} modifiedTime: ${file.modifiedTime}');
  }

  try {
    final _file = await driveApi.files.get(folderId,
      $fields: 'files(id, name, mimeType, createdTime, modifiedTime)',
    ) as drive.File;
    // ファイル情報が取得できた場合の処理
    log.d('${_file.name} createdTime: ${_file.createdTime} modifiedTime: ${_file.modifiedTime} ');
  } catch (e) {
    // ファイルが見つからなかった場合やエラーが発生した場合の処理
    print('エラー: $e');
  }

  client.close();
}

 

ChatGPTに聞くと

google drive のファイルのタイムスタンプを取得する事はできますか?

はい、Google Driveのファイルのタイムスタンプを取得することは可能です。Google Drive APIを使用してファイルのメタデータを取得することで、ファイルの作成日時や最終更新日時などの情報を取得できます。

以下は、Google Driveファイルのメタデータを取得して、作成日時と最終更新日時を取得するDartコードの例です。

import 'package:googleapis/drive/v3.dart' as drive;
import 'package:googleapis_auth/auth_io.dart';

Future<void> getFileTimestamp(String fileId) async {
  final jsonString = await rootBundle.loadString('assets/google_sheet_auth.json');
  final jsonData = json.decode(jsonString);
  final credentials = ServiceAccountCredentials.fromJson(jsonData);
  final scopes = [drive.DriveApi.driveMetadataReadonlyScope];

  final client = await clientViaServiceAccount(credentials, scopes);
  final driveApi = drive.DriveApi(client);

  try {
    final file = await driveApi.files.get(fileId);
    final createdTime = file.createdTime;
    final modifiedTime = file.modifiedTime;

    print('ファイルの作成日時: $createdTime');
    print('ファイルの最終更新日時: $modifiedTime');
  } catch (e) {
    print('エラー: $e');
  } finally {
    client.close();
  }
}

void main() {
  getFileTimestamp('YOUR_GOOGLE_DRIVE_FILE_ID');
}

ってことで、上記では取得できませんでした。

AppStore のスクリーンショットは、以下のスクリーンショットが必要となります。

iPhone 6.5インチディスプレイ(必須)
iPhone 5.5インチディスプレイ(必須)
iPad Pro(第6世代)12.9インチディスプレイ(必須)
iPad Pro(第2世代)12.9インチディスプレイ(必須)

シュミレータで取得するには、以下のシュミレータを指定します。

6.5 -> iPhone 13 Pro

5.5 -> iPhone 8 Plus

12.9 -> iPod Pro (12.9-inch)

スクリーンショットを切り欠き(ノッチ)無しで取得するには、Simulator  メニューの File -> Save Screen より取得します。

そのままでは、正常に動作しない。

設定->システム->言語入力->詳細設定->テキスト読み上げ再生->再生 で動作を確認

google service speech を google play で最新更新

 

一つの Google spread sheet を複数のシートに分割するには?

function splitSheetIntoRows(sourceSheetName, numRowsPerSheet) {
  var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sourceSheetName);
  if (!sourceSheet) {
    throw new Error("指定されたシートが見つかりませんでした。");
  }
  
  var lastRow = sourceSheet.getLastRow();
  var sourceData = sourceSheet.getRange(1, 1, lastRow, sourceSheet.getLastColumn()).getValues();
  
  for (var i = 0; i < lastRow; i += numRowsPerSheet) {
    var numRows = Math.min(numRowsPerSheet, lastRow - i);
  // sheet 名の設定
    var newSheetName = sourceSheetName + "_" + (i + 1) + "-" + (i + numRows);
    var newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(newSheetName);
    newSheet.getRange(1, 1, numRows, sourceSheet.getLastColumn()).setValues(sourceData.slice(i, i + numRows));
  }
}

function main() {
  var sourceSheetName = "ALL"; // 分割元のシート名をここに指定
  var numRowsPerSheet = 25; // 分割する行数をここに指定
  splitSheetIntoRows(sourceSheetName, numRowsPerSheet);
}

 

Container(
          width: double.infinity,
          height: 1,
          color: Colors.black,
        ),

全体のソース

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Draw 3 Lines Across Screen Width'),
        ),
        body: Column(
          children: [
            SizedBox(height: 10,),
            Container(
              width: double.infinity,
              height: 5,
              color: Colors.blue,
            ),
            SizedBox(height: 10,),
            Container(
              width: double.infinity,
              height: 5,
              color: Colors.red,
            ),
            SizedBox(height: 10,),
            Container(
              width: double.infinity,
              height: 5,
              color: Colors.green,
            ),
          ],
        ),
      ),
    );
  }
}

flutterfire configure
FlutterAppRequiredException: The current directory does not appear to be a Flutter application project.

実行しているフォルダがプロジェクトのフォルダでは無いと言われるが、しかしフォルダは間違っていない

pubspec.yaml を見比べると

dependencies:
  flutter:       # add
    sdk: flutter  # add

ちょっと他と違ったので、2行追加 で正常に動作

flutter で、versionName versionCode の設定するには?

pubspec.yaml にて

# flutter で、versionName versionCode の設定する
version: 1.2.0+4

versionName が 1.2.0

versionCode が4 になります。

iOS だとbuild-name build-number となります。

コマンド実行で指定するには

flutter build appbundle --build-name=1.3.0 --build-number=4  
flutter build ios --build-name=1.3.0 --build-number=4

 

 

Module 'app_tracking_transparency' not found

flutter からの起動は正常なのに、xcode からだと基本的にライブラリーが見つからない!

Module ‘xxx’ not found

 

runner.xcworkspace を開く

build でビルド成功

 

Xcodeを14.3にアップデートしてからArchive 作成時

// Xcodeを14.3にアップデートしてからArchive
mkdir -p /Users/falco/Library/Developer/Xcode/DerivedData/Runner-bizyteowoysewphgsajrrszlkzyw/Build/Intermediates.noindex/ArchiveIntermediates/Runner/BuildProductsPath/Release-iphoneos/Runner.app/Frameworks
Symlinked...
rsync --delete -av --filter P .*.?????? --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "../../../IntermediateBuildFilesPath/UninstalledProducts/iphoneos/FMDB.framework" "/Users/falco/Library/Developer/Xcode/DerivedData/Runner-bizyteowoysewphgsajrrszlkzyw/Build/Intermediates.noindex/ArchiveIntermediates/Runner/InstallationBuildProductsLocation/Applications/Runner.app/Frameworks"
building file list ... rsync: link_stat "/Users/falco/AndroidProjects/SharedStudy/ios/../../../IntermediateBuildFilesPath/UninstalledProducts/iphoneos/FMDB.framework" failed: No such file or directory (2)
done

sent 29 bytes  received 20 bytes  98.00 bytes/sec
total size is 0  speedup is 0.00
rsync error: some files could not be transferred (code 23) at /AppleInternal/Library/BuildRoots/aaefcfd1-5c95-11ed-8734-2e32217d8374/Library/Caches/com.apple.xbs/Sources/rsync/rsync/main.c(996) [sender=2.6.9]
Command PhaseScriptExecution failed with a nonzero exit code

bulid script を修正(source=”$(readlink “${source}”)”を検索)

if [ -L "${source}" ]; then
  echo "Symlinked..."
  # source="$(readlink "${source}")"
  source="$(readlink -f "${source}")"
fi

source=”$(readlink “${source}”)”

source=”$(readlink -f “${source}”)”

© 2024 Falco Tech Blog Suffusion theme by Sayontan Sinha