KINTOテクノロゞヌズのブログ - TECH PLAY

TECH PLAY

KINTOテクノロゞヌズ

KINTOテクノロゞヌズ の技術ブログ

å…š1132ä»¶

この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の11日目の蚘事です🎅🎄 メリヌクリスマス✌🎅 KINTOテクノロゞヌズで my route(iOS) を開発しおいるRyommです。 本蚘事ではカスタムスタむルの玹介をしたす。 はじめに 私がこの曞き方を知ったのはApp Dev Tutorialsがきっかけです。 https://developer.apple.com/tutorials/app-dev-training/creating-a-card-view#Customize-the-label-style なんおスタむリッシュなんだ  カスタムスタむルを䜿うこずで、SwiftUIのコヌドが栌段に読みやすく、掗緎されたコヌドになる 私もスタむリッシュなコヌドを曞きたい そんな衝動に駆られお䜿い始めたしたが、実際かなり䟿利で読みやすいのでおすすめです。 特に、甚途別に乱立した構造䜓名を芚えおいなくおも ~~Style() にドットで候補を探せるずころが気に入っおいたす。 カスタムスタむルの぀くりかた 䟋えばLabelのカスタムスタむルを䜜成する堎合、 LabelStyle を継承した構造䜓を䜜成し、プロトコルに準拠したメ゜ッドここでは makeBody(configuration:) 内にスタむルを定矩したす。 configurationに含たれる倀はものによっお異なるので郜床調べる必芁がありたすが、LabelStyleConfigurationに関しおはTextずImageのViewが入っおいたす。 /// 文字+アむコン のラベルスタむル struct TrailingIconLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { HStack { configuration.title configuration.icon } } } さらに LabelStyle を拡匵しお、䜜成したカスタムスタむルを静的プロパティずしお远加するず、呌び出し時に .labelStyle(.trailingIcon) のように呌び出すこずができお可読性が向䞊したす。〜 extension LabelStyle where Self == TrailingIconLabelStyle { static var trailingIcon: Self { Self() } } もし「spaceを指定したい」など、匕数を持たせたい堎合はカスタムスタむルにメンバプロパティを远加するこずで実珟できたす。 /// 文字+アむコン のラベルスタむル struct TrailingIconLabelStyle: LabelStyle { // デフォルト倀を蚭定しおおくずドット始たりの呌び出し方法もキヌプできる var spacing: CGFloat = 4 func makeBody(configuration: Configuration) -> some View { HStack(spacing: spacing) { configuration.title configuration.icon } } } // 呌び出し Label().labelStyle(.trailingIcon) // spaceにはデフォルト倀が䜿われる Label().labelStyle(TrailingIconLabelStyle(spacing: 2)) // spaceを2に指定 䜿いどころ アプリ党䜓で広く䜿う共通デザむンや、䞊蚘の TrailingIconLabelStyle のように普遍的なカスタムスタむルに䜿うず良いでしょう。 たずえば、my routeではProgressViewで䜿っおいたす。 ProgressView自䜓のスタむル蚭定もですが、ProgressViewを衚瀺䞭に背景をグレヌっぜくするのもスタむルに含めるこずができたす。 struct CommonProgressViewStyle: ProgressViewStyle { func makeBody(configuration: Configuration) -> some View { ZStack { ProgressView(configuration) .tint(Color(.gray)) .controlSize(.large) Color(.loadingBackground) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } extension ProgressViewStyle where Self == CommonProgressViewStyle { static var common: Self { Self() } } ちなみに、ProgressViewに background() を指定するずProgressViewに必芁なサむズのみしか描画されないので、ZStackでColorをProgressViewの䞋に敷き、背景色が䞎えられたサむズ党䜓に広がるようにしおいたす。 このようにスタむルを䜜成するこずで、以䞋のように簡朔でスタむリッシュに曞けるようになりたした。 struct SomeView: View { @State var loadingStatus: LoadingStatus var body: some View { SomeContentView .overlay { if loadingStatus == .loading { ProgressView() .progressViewStyle(.common) } } .disabled(loadingStatus == .loading) } } おわりに カスタムスタむルの玹介でした 以䞋のペヌゞにあるものはカスタムスタむルを䜜成できたす。 https://developer.apple.com/documentation/swiftui/view-styles よりスタむリッシュなコヌドを目指しお䞀歩前進 🊌 🎄
This article is part of day 11 of KINTO Technologies Advent Calendar 2024 Merry Christmas! ✌ I'm Ryomm, and I work on developing the My Route iOS app at KINTO Technologies. In this article, I will introduce custom styles. Introduction App Dev Tutorials were the reason I learned to create custom styles. https://developer.apple.com/tutorials/app-dev-training/creating-a-card-view#Customize-the-label-style How stylish...! Using custom styles significantly enhances the readability and sophistication of SwiftUI code...! "I want to write stylish code, too!" Initially, that was what inspired me to start using it, but now I recommend it because it’s genuinely convenient and makes the code much easier to read. What I particularly like is that you can search for options using dots in ~~Style() even if you don’t remember the specific structure names, as they are organized based on their purpose. How to create a custom style For example, if you want to create a custom style for a Label, create a structure that inherits LabelStyle and define the style in a protocol-compliant method (in this case makeBody(configuration:) ). The values within the configuration object vary depending on what you're creating, so it's important to check each time. For LabelStyleConfiguration, it includes Text and Image views. /// Character + Icon LabelStyle struct TrailingIconLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { HStack { configuration.title configuration.icon } } } You can also extend LabelStyle to add your custom style as a static property, which can be called as .labelStyle(.trailingIcon) when invoked, and improve readability. So~ stylish! extension LabelStyle where Self == TrailingIconLabelStyle { static var trailingIcon: Self { Self() } } If you want to have a parameter, such as "specify a space," you can do this by adding a member property to your custom style. /// Character + Icon LabelStyle struct TrailingIconLabelStyle: LabelStyle { // you can set the default value to preserve the way the dot starts are called var spacing: CGFloat = 4 func makeBody(configuration: Configuration) -> some View { HStack(spacing: spacing) { configuration.title configuration.icon } } } // call The default value is used in Label().labelStyle(.trailingIcon) // space Label().labelStyle(TrailingIconLabelStyle(spacing: 2)) // Set space to 2 Uses You can use it for common designs that you use widely throughout apps, or for universal custom styles like TrailingIconLabelStyle above. For example, my route uses it in ProgressView. While ProgressView itself is styled, you can also include a grayish background when ProgressView is displayed. struct CommonProgressViewStyle: ProgressViewStyle { func makeBody(configuration: Configuration) -> some View { ZStack { ProgressView(configuration) .tint(Color(.gray)) .controlSize(.large) Color(.loadingBackground) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } extension ProgressViewStyle where Self == CommonProgressViewStyle { static var common: Self { Self() } } By the way, when you use background() with a ProgressView, it only applies to the area required by the ProgressView. To ensure the background color covers a larger area, you can use a ZStack to place the color beneath the ProgressView, allowing the background to expand to the desired size. By defining a style in this way, you can achieve concise and elegant code, as shown in the example below. struct SomeView: View { @State var loadingStatus: LoadingStatus var body: some View { SomeContentView .overlay { if loadingStatus == .loading { ProgressView() .progressViewStyle(.common) } } .disabled(loadingStatus == .loading) } } Conclusion That wraps up this introduction to custom styles! You can create custom styles on the following page. https://developer.apple.com/documentation/swiftui/view-styles Take a step toward writing more stylish and elegant code!
Introduction Hello, we are Chang and Hosaka, in charge of the my route by KINTO iOS app development in the Mobile App Development Group. In our mobile app development group, we usually use GitHub Actions as a CI/CD tool. This time, we introduced Bitrise for the first time to the my route by KINTO iOS app, so we would like to talk about it. What is Bitrise ? Bitrise is a cloud-based CI/CD (continuous integration / continuous delivery) service for the automated building, testing, and deployment of mobile apps. Bitrise is designed to streamline mobile app development, and it supports major mobile app development frameworks such as iOS, Android, React Native, and Flutter. Some of the key features of Bitrise include: Build Automation Builds are automatically triggered when code in the repository is updated. Builds can be easily configured using the visual interface. Test Automation Tests are automatically run after the builds are complete. Bitrise supports integration with a variety of testing tools, allowing automation at different test levels, including unit testing and UI testing. Automated Deployment If the test passes, Bitrise will automatically take steps to deploy the app. Bitrise supports deployment to app stores such as the App Store and Google Play. Variety of Integration Bitrise supports integration with a variety of tools and services, including GitHub, Bitbucket, Slack, Jira, Firebase, and more. Cloud-based Services Bitrise is a cloud-based service that does not require infrastructure configuration or management. Developers can easily take advantage of Bitrise’s features. Bitrise is a powerful tool for streamlining and improving the quality of mobile app development, and is a very valuable CI/CD service to developers and development teams. The Reason for Introducing Bitrise There are two reasons why we implemented Bitrise in the my route by KINTO iOS app. We had an opportunity to hear from Bitrise Ltd. before building a CI/CD environment, and at that time all the team members had replaced their PCs from Intel to M1, so we were fascinated by Bitrise, which can be built in the same M1 environment. The results of the experiment below, comparing Bitrise and Github Actions on an Intel Medium machine (the lowest performance), show that the cost can be reduced by about 30% and the processing time can be shortened by about 50%. Bitrise and GitHub Actions performance comparison experiment (tested in a different app): Processing Time Comparison Experimental Attempt / Machine Name Bitrise (Intel Medium) Github Actions 1 07:48 16:24 2 11:42 16:18 3 06:53 16:09 Average 08:48 16:17 Cost Comparison Github Actions cost per minute is $0.08 Bitrise cost per minute is $0.106666 Bitrise calculation: Given that 1 credit (cr.) = elapsed minutes (min.) × machine spec (2)... (i), and $400/7,500 credit = apprrx. 0.05333($/cr)... (ii), For (i) and (ii), apprx. 0.05333($/cr.) × 2(cr./min.) = apprx. 0.106666 ($/min.) Experimental Count / Machine Name Bitrise (Intel Medium) Github Actions 1 0.85 1.36 2 1.28 1.36 3 0.75 1.36 Average 0.96 1.36 Using Bitrise in my route by KINTO To implement Bitrise in my route KINTO, we signed up for Bitrise's Teams pricing plan and adopted an M1 Medium machine consuming 2 credits per minute. The Teams plan has a credit limit set according to the price, and exceeding that limit incurs additional costs, so we aimed to optimize costs by also using GitHub Actions. With GitHub Actions, Linux is 1/10 the cost of macOS . Therefore, we use GitHub Actions for steps that can run on Linux (no app build required) and Bitrise for steps that require macOS (app build required). Bitrise Workflow my route by KINTO, mainly automates the following: unit testing, deployment to App Store and TestFlight, and build result notifications to Slack. Currently, builds are triggered by pushing to the develop and release branches, and scheduled builds are done on weekday mornings. We have observed that a single build takes about 6-11 minutes (12-22 credits). GitHub Actions Workflow GitHub Actions automates the static analysis flow. SwiftLint: A static analysis tool for Swift that automatically points out any code violations in the PR. SonarQube: A static analysis tool that analyzes code duplication and other issues that SwiftLint cannot cover. Summary and Future Prospects Looking ahead, Bitrise is expected to continue to expand and improve its features to meet the needs of mobile app development. For example, we can expect more advanced testing and deployment options, more flexible workflow settings, and further expansion of cloud-based resources. It is also expected to provide a more seamless development experience, including collaboration with the developer community and improved integration with other tools. KINTO Technologies would like to keep a close eye on the trends and lead to further utilization of this technology. Here is the review. https://findy-tools.io/products/bitrise/18/39
GitHub Actionsだけで実珟するKubernetesアプリケヌションのContinuous Delivery こんにちは。Toyota Woven City Payment Solution開発グルヌプの楢厎ず申したす。 我々は、 Woven by Toyota で Toyota Woven City で利甚される決枈基盀アプリケヌションの開発に携わっおおり、バック゚ンドからWebフロント゚ンド、モバむルアプリケヌションたで決枈に関わる機胜を暪断的に開発しおいたす。 決枈バック゚ンドはKubernetes䞊で動䜜し、いわゆるクラりドネむティブなツヌル矀を䜿っお開発しおいたす。 今回はKubernetesアプリケヌションを構築・安定運甚しおいく䞊でキヌずなる、GitOps(Gitでむンフラ構成ファむルを管理し倉曎を適甚指定する運甚方法)を螏襲し぀぀、CD(Continuous Delivery)プロセスに関しお、䞀般に甚いられおいる、いわゆる「クラりドネむティブなツヌル」ではなく、GitHub Actionsだけで構築するこずを目指したす。 ここでいうCDの機胜はあくたで Kubernetesの構成管理ファむルの倉曎の適甚 コンテナむメヌゞのアップデヌト です。他にもBlue / GreenやCanaryデプロむなど応甚的なCDプロセスはありたすが、「小さくスタヌト」するこずを想定しおいたす。既にDevOpsチヌムが組成されおいお、その恩恵にあやかれる人ではなく、最小の開発人数で、か぀新たなツヌルなしに普段利甚しおいるGitHub Actionsのみを利甚しおKubernetes䞊で動䜜するアプリケヌションを生産性高く継続的デリバリヌさせたい人が察象になりたす。 レポゞトリもアプリケヌションのコヌドずKubernetesの構成管理ファむルを同じレポゞトリで管理しおいるこずを想定しおいたす。(暩限の蚭定次第ではレポゞトリをたたいで実行可胜だずは思いたすがここでは觊れたせん) (Gitlabを普段お䜿いの方であれば Auto DevOps ずいう非垞に優秀なツヌルがあるので、決しおGitHub及びGitHub Actions最高ずいう぀もりはありたせんので悪しからず) クラりドネむティブなKubernetes向けCI/CDツヌル Kubernetes向けアプリケヌションのCI/CDず聞いお読者の皆さんはどのようなツヌルを思い぀きたすか Argo CD Flux CD PipeCD Tekton などが挙げられたす。 いずれのツヌルも非垞に高機胜で、Kubernetesの機胜をフルに掻かすために非垞に有甚です。 たたGitOpsを実践する䞊で、Kubernetesの構成ファむルやアプリケヌションむメヌゞを柔軟に安党に曎新できたす。 䞀方でツヌル特有の知識や運甚も必芁で、DevOpsに専門の人員がいるような倧きな組織ではないず継続的に運甚するのは難しいのではないでしょうか CDツヌルの運甚そのものにKubernetesが必芁で、Kubernetesの構成ファむルを管理するツヌルのためにKubernetesの構成ファむルが必芁ずいうこずで、少人数の組織では導入や運甚の敷居も非垞に高いず思っおいたす。 この蚘事では以䞋の図のようなパむプラむンを、GitHub Actionsだけで構成するこずを考えたす。 Kubernetesは特定のクラりドではなく汎甚的なクラスタを想定しおいたす。䜕かしらのコンテナレゞストリがあるこずを想定しおいたす。構成管理ファむルはKustomizeを䟋にしたすが、Helm, Terraformなど䜕でも応甚可胜だず思いたす。 flowchart TD A[コヌドの倉曎] -->|ビルドパむプラむンを実行| B[コンテナむメヌゞのビルド、プッシュ] B -->|コンテナむメヌゞ曎新甚のパむプラむンが起動| C[新しいコンテナむメヌゞでkustomizationを曞き換えたプルリク゚ストができる] C -->|プルリク゚ストのレビュヌ| D[新しいコンテナむメヌゞをKubernetesにデプロむ] linkStyle default stroke-width:2px,color:blue,stroke-dasharray:0 デモ Kubernetesの構成ファむルを管理するフォルダずアプリケヌションのフォルダが入っおいるレポゞトリを考えたす。 フォルダ構成は以䞋になりたす。ここでは具䜓的なコヌドやDockerfileの䞭身、各アプリケヌションの゜ヌス等は省略したす。 ├── .github │   ├── actions │   │   └── image-tag-update │   │   └── action.yaml │   └── workflows │   ├── build-go.yaml │   ├── build-java.yaml │   ├── build-node.yaml │   └── kubectl.yaml ├── go-app │   ├── src/ │   └── Dockerfile ├── java-app │   ├── src/ │   └── Dockerfile ├── k8s │   ├── service-go.yaml │   ├── service-java.yaml │   ├── service-node.yaml │   └── kustomization.yaml └── node-app    ├── src/ └── Dockerfile それぞれのアプリケヌションは以䞋のような圢でplaceholderずし、 apiVersion: apps/v1 kind: Deployment metadata: name: app spec: ... template: ... spec: containers: - name: some-server image: go-placeholder # placeholderずしおkustomizationず同じ文字列を入れおおく その耇数のplaceholderをkustomization.yaml䞊で䞀元管理したす。 apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: techblog resources: - service-go.yaml - service-java.yaml - service-node.yaml images: - name: go-placeholder newName: go-app newTag: v1.1.1 - name: java-placeholder newName: java-app newTag: v2.7.9alpha - name: node-placeholder newName: node-app newTag: latest たずKubernetesの構成ファむルを適甚するために、以䞋のようなyamlのGitHub Actionsを構成したす。 name: kubectl on: pull_request: branches: - "**" paths: - "k8s/**" #Kubernetesのmanifest fileが入っおいる堎所 push: branches: - main paths: - "k8s/**" jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: azure/setup-kubectl@v4 - env: KUBECONFIG_CONTENTS: ${{ secrets.KUBECONFIG_CONTENTS }} #事前にkubeconfigをGitHubのシヌクレットに栌玍しおおく run: | echo "${KUBECONFIG_CONTENTS}" > $HOME/.kube/config chmod 600 $HOME/.kube/config - run: kubectl apply --dry-run=server -k ./k8s >> $GITHUB_STEP_SUMMARY - if: github.ref == 'refs/heads/main # mainブランチだったら実際に適甚 run: kubectl apply -k k8s/ これは管理者暩限を持ったkubeconfigが手元にある堎合の䞀般的なKubernetesの構成ファむルを適甚するパむプラむンです。 各クラりドなどのクラスタの蚭定方法に応じおconfigの取埗方法は倉曎しおください。 次に各アプリケヌションをpushする際に自動でプルリク゚ストを䜜るか、コンテナのむメヌゞタグを曞き換えるcompositeを䜜成したす。 name: image-tag-update description: 'コンテナむメヌゞの曎新時にkustomizationのむメヌゞタグを曞き換えるタスク' inputs: target_app: description: '察象のアプリケヌション' required: true tag_value: description: '新しいコンテナむメヌゞタグ' required: true token: description: 'PRや内容の曎新の暩限を持ったトヌクン' required: true runs: using: 'composite' steps: - uses: actions/checkout@v4 id: check-branch-exists continue-on-error: true with: ref: "image-tag-update" # タグ曎新甚のデフォルトブランチ名 - uses: actions/checkout@v4 # checkoutはブランチが存圚しないずデフォルトブランチにフォヌルバック、みたいなこずはできないのでこういう曞き方 if: steps.check-branch-exists.outcome == 'failure' with: ref: main - uses: mikefarah/yq@master # yqで察象のplaceholderのタグの倀を眮換 with: cmd: yq eval '(.images[] | select(.name == "'"${{ inputs.target_app }}-placeholder"'")).newTag = "'"${{ inputs.tag_value }}"'"' -i k8s/kustomization.yaml - uses: peter-evans/create-pull-request@v6 if: steps.check-branch-exists.outcome == 'failure' # プルリク゚ストがないず新しいプルリク゚ストを䜜成 with: title: 'コンテナむメヌゞの曎新' body: | `${{ inputs.target_app }}` を曎新したす branch: "image-tag-update" - uses: stefanzweifel/git-auto-commit-action@v5 if: steps.check-branch-exists.outcome == 'success' # チェックアりトが成功したら、既存のブランチにコミットを远加 with: commit_message: "Image update for ${{ inputs.target_app }}" 各アプリケヌションのむメヌゞを䜜成する過皋で、䞊蚘のcompositeを呌びたす。耇数のアプリケヌションを管理しおいる堎合は、それぞれのアプリケヌションの埌に付け加えるずよいでしょう。 ... - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v6 with: file: ./Dockerfile push: true tags: ${{ env.tag }} # なにかしらのタグ - uses: ./.github/actions/image_update if: github.ref == 'refs/heads/main' with: target_app: go tag_value: ${{ env.tag }} token: ${{ secrets.GITHUB_TOKEN }} # コンテンツ、プルリク゚スト線集暩限を持ったgithub token これで、アプリケヌションを実行するタむミングでコンテナむメヌゞが自動でアップデヌトされ、プルリク゚ストベヌスで新しいコンテナむメヌゞがデプロむできるようになりたす (タグの導出方法は各自のワヌクフロヌにおたかせしたす。䞋蚘の䟋はマむナヌバヌゞョンをむンクリメントした䟋です) - name: go-placeholder newName: go-app - newTag: v1.1.1 + newTag: v1.1.2 運甚䞊の泚意点 デプロむのタむミング むメヌゞ曎新甚のプルリク゚ストが、マヌゞされた瞬間にデプロむされたす。むンフラの構成ファむルの修正ず合わせおリリヌスしたい堎合は、このブランチに修正を远加するか、マヌゞするタむミングを合わせお適甚するずいいでしょう。 新芏コンテナアプリケヌションの远加 䟋えば䞊蚘の䟋でPythonのアプリケヌションを足したいずいう時に、むメヌゞ曎新甚のプルリク゚ストがそのたた残っおいるず、Pythonのむメヌゞタグをどれだけ曎新しおもプルリク゚スト自䜓に最新版の倉曎が反映されおない限り、空振りし続けるので泚意が必芁です。 切り戻し Commitをrevertすれば戻せるので非垞にシンプルです。 Reconcileのタむミング GitOps Toolの倚くがドリフトを抑制するためのリコンサむルをほがリアルタむムで実斜できるのに察しお、このやり方だずCDパむプラむンが動䜜したタむミングでないず実斜できたせん。 Kubernetesのクラスタに曎新暩限をどれくらいのチヌムメむトが保有しお暩限を行䜿しおいるかにも応じおツヌルの䜿い分けは倧事だず思いたす。 Container Registryを盎接芋おいるわけではない コンテナレゞストリから盎接コンテナむメヌゞの最新版を取埗するものもありたすが、この方法では実際に芋おいるわけではありたせん。確実にコンテナが存圚するか、確認するステップをコンテナレゞストリごずに実装したほうが良さそうです。 GitHub Actionsの暩限蚭定に関しお contents ず pull-requests の曎新暩限が必芁になっおきたす。Actionsのパヌミッション、GitHub Appなどに暩限をアサむンしお䜿っおください。詳しくは こちら 。 埌に実行されたコンテナむメヌゞで䞊曞きされる CDツヌルには、コンテナむメヌゞのタグの倀をみお、Semantic Versioningなどの芏則に埓っおどちらが新しいバヌゞョンか刀別する仕組みがありたす。 䞊蚘で瀺したworkflowはタグの倀に関係なく、埌に実行されたパむプラむンでむメヌゞタグを䞊曞きしたす。 この挙動が問題であれば倀を怜蚌しお、䞊曞きすべきか刀定する必芁がありたす。 たずめ このやり方を甚いるこずで、GitOpsがGitHub䞊で完結しお、非垞にシンプルにKubernetesアプリケヌションの継続的デリバリヌが実践できるのではないのかなず思いたす。 CDツヌルの゚ラヌもGitHub Actions䞊に集玄できるので、普段のCIプロセスず同じ方法で実行結果や゚ラヌの内容が確認できるのは非垞に嬉しいですね。 色々なツヌルが倚く存圚し、目移りするこずも倚いKubernetesのツヌル遞定ですが、身の䞈にあったツヌルを利甚しおKubernetesアプリケヌション開発の生産性を高めおいきたいですね。
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の10日目の蚘事です🎅🎄 Background When developing the KINTO かんたん申し蟌みアプリ App, we implemented some shared code using KMP (Kotlin Multiplatform) and published it as a Swift Package. This approach allowed us to efficiently share code across platforms and simplify the development process by avoiding code duplication. Our iOS Team currently uses XcodeGen to manage dependencies, and importing KMP code can be as simple as making a 4-line modification to the project.yml file. Here is an example of such a modification: packages: + Shared: + url: https://github.com/[your organization]/private-android-repository + minorVersion: 1.0.0 targets: App: dependencies: + - package: Shared - package: ... However, since our code resides in private repositories, some additional setup is required. This blog will outline those steps and explain how we streamlined the process. About Package.swift Here’s a brief explanation of how we publish KMP code as a Swift Package: Compile the KMP code into an .xcframework . Package the .xcframework into a zip file and calculate its checksum. Create a new release page on GitHub and upload the zip file as part of the release assets. Obtain the zip file’s URL from the release page. Generate the Package.swift file based on the URL and checksum. Commit the Package.swift file and add a git tag to mark the release. Associate the git tag with the release page and officially publish the GitHub release. The resulting Package.swift file will look something like this: // swift-tools-version: 5.10 import PackageDescription let packageName = "Shared" let package = Package( name: packageName, ... targets: [ .binaryTarget( name: packageName, url: "https://api.github.com/repos/[your organization]/private-android-repository/releases/assets/<asset_id>.zip", checksum: "<checksum>" ) ] ) Permission Setup for Development Environment Since the URL resides in a private repository, you will encounter the following error if no permission configuration is done: To resolve this, we explore two options: .netrc files and Keychain. Option 1: Using a .netrc File You can store your GitHub credentials in a .netrc file, which is a simple way to authenticate API requests: #Example: echo "machine api.github.com login username password ghp_AbCdEf1234567890" >> ~/.netrc echo "machine api.github.com login <Your Github Username> password <Your Personal Access Token>" >> ~/.netrc This method is quick and effective but may pose security risks since the token is stored in plaintext. Option 2: Using Keychain If you prefer not to store the token in plaintext, you can use Keychain to securely store your credentials: Open Keychain Access.app . Select ①, the login keychain. Select ②, to create a new Password Item. In the dialog box, enter the following information: Keychain Item Name: https://api.github.com Account Name: Your GitHub username Password: Your Personal Access Token This approach is more secure and integrates seamlessly with macOS authentication mechanisms. For SSH Users The above instructions assume you cloned the iOS repository using the https protocol. If you did, you already have the necessary permissions for github.com configured. However, if you cloned the repository using the ssh protocol, you might lack permissions for github.com , leading to permission-related errors during the resolveDependencies phase. To fix this, you can add an entry for the domain github.com in the .netrc file: #Example: echo "machine github.com login username password ghp_AbCdEf1234567890" >> ~/.netrc echo "machine github.com login <Your Github Username> password <Your Personal Access Token>" >> ~/.netrc Alternatively, use Keychain Access to add an item with the name https://github.com . Either method ensures your system has the required permissions. GitHub Actions After resolving the local development environment issues, you also need to address permission issues in the CI environment to ensure smooth automation during builds. Retrieving Tokens in GitHub Actions Using a Personal Token One straightforward approach is to create a Personal Access Token (PAT) with access to private repositories and pass it to the CI environment via Actions secrets. While effective, this method has several drawbacks: Token Expiration Tokens with an expiration date require periodic updates, and forgetting to update them may cause CI failures. Tokens without an expiration date pose long-term security risks. Broad Permissions A personal account usually has access to multiple private repositories, making it difficult to restrict PAT permissions to a single repository. Personal Dependency If the account owner loses access to private repositories due to role changes, CI workflows will fail. Using a GitHub App Using a GitHub App is a more robust solution, offering several advantages: Fine-grained permissions for repositories No dependency on individual accounts Temporary tokens that enhance security Setting Up a GitHub App We ultimately used a GitHub App to configure access permissions. Here is the process: Create a GitHub App in your organization. Install the App in both iOS and Android projects to manage repository access. Configure the App’s AppID and Private Key in the iOS project’s Actions secrets. Add code in the workflows to retrieve a temporary Access Token. Here’s an example: steps: - name: create app token uses: actions/create-github-app-token@v1 id: app-token with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} owner: "YourOrgName" - name: set access token for private repository shell: bash env: ACCESS_TOKEN: ${{ steps.app-token.outputs.token }} run: | git config --global url."https://x-access-token:$ACCESS_TOKEN@github.com/".insteadOf "https://github.com/" touch ~/.netrc echo "machine github.com login x-access-token password $ACCESS_TOKEN" >> ~/.netrc echo "machine api.github.com login x-access-token password $ACCESS_TOKEN" >> ~/.netrc By using a GitHub App, we ensure our CI workflows are secure, efficient, and free from dependency on individual user accounts. This approach minimizes risk and streamlines development across teams.
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の10日目の蚘事です🎅🎄 「Mobility Night」は、モビリティ領域の゜フトりェア技術者、ビゞネスパヌ゜ン、研究者、プロダクトマネヌゞャヌなどが気軜に集たり、業界特有の知芋や課題を共有する勉匷䌚シリヌズです。#0初回のクロヌズド開催を経お、いよいよ第1回#1をオヌプンな圢で開催するこずができたした。 今回のテヌマは、モビリティサヌビスの基盀技術である「GPS・䜍眮情報」。カヌナビや地図アプリ、オンデマンド亀通、そしお将来の自動運転やスマヌトシティ基盀たで、「いたどこにいるか」を正確に把握し、掻甚するこずはサヌビス䟡倀の根幹を支えたす。 圓日は以䞋の5぀のセッションが行われ、GPS・䜍眮情報技術を軞に、それぞれが独自の切り口から課題ず可胜性を瀺したした。 この蚘事は技術広報グルヌプでMobility Nightの䌁画運営も行っおいる䞭西が執筆しおいたす。 1. Exploring New Google Places API 登壇者: KINTOテクノロゞヌズ株匏䌚瀟 numaさん Google Places APIは地図プラットフォヌムの䞭栞機胜の䞀぀であり、呚蟺怜玢や斜蚭情報取埗を効率的に行うための重芁なむンタヌフェヌスです。このセッションでは、テキスト入力䞭から即時に候補を提瀺するAutocomplete機胜の匷化や、Fieldsパラメヌタによる必芁情報の絞り蟌みなど、最新の改善点が玹介されたした。 ポむント: パフォヌマンスずコスト最適化: Fields指定で䞍芁なデヌタ取埗を削枛し、APIコストを抑えるずずもにレスポンス高速化が可胜。 ナヌザヌ゚クスペリ゚ンス向䞊: 欲しい情報に玠早くアクセスできる䜓隓は、移動䞭のナヌザヌにずっお倧きな利点。Autocomplete匷化で怜玢負荷を軜枛し、UXを磚き䞊げる。 将来ぞの展望: 珟圚は䜍眮情報取埗が䞭心だが、将来的にはIoTセンサヌや行動履歎分析ず組み合わせたパヌ゜ナラむズ戊略も期埅できる。 https://speakerdeck.com/kintotechdev/exploring-new-google-places-api 2. AIドラレコサヌビスの走行デヌタで䜜る䜍眮情報デヌタプロダクト 登壇者: GO株匏䌚瀟 束浊慎平さん ドラむブレコヌダヌは事故蚘録甚のデバむスずいう印象が匷いですが、このセッションでは「走行デヌタ街をセンサヌ化するプラットフォヌム」ずしお再解釈されたした。映像GPSデヌタをAI解析するこずで、道路暙識や信号、舗装工事などの情報を動的に地図ぞ反映できる可胜性が瀺唆されたした。 ポむント: ダむナミックな地図曎新: 静的だった地図を“生きた情報基盀”ぞ進化させ、道路むンフラ倉化をほがリアルタむムで反映。 耇数車䞡デヌタの統合: 異なる車䞡から埗られるデヌタを突き合わせるこずで、䞀時的な暙識や工事箇所などを高粟床に怜出。 プラむバシヌ察策: 個人情報が映り蟌む映像を適切に匿名化し぀぀道路情報を保持する技術・運甚が必須。 将来的応甚: 自動運転甚HDマップ敎備、スマヌトシティ蚈画、新サヌビス創出など、倚面的なビゞネス展開が期埅。 https://speakerdeck.com/pemugi/aidorarekosabisunozou-xing-deta-dezuo-ruwei-zhi-qing-bao-detapurodakuto-wei-zhi-qing-bao-jing-du-xiang-shang-nogong-fu 3. GPSモゞュヌルを觊っお孊ぶ、衛星枬䜍技術の抂芁 登壇者: チャリチャリ株匏䌚瀟 VP of Engineering 蛭田慎也さん GPSは圓たり前に利甚されおいたすが、郜垂環境では電波反射や芖界䞍良、衛星数の偏りなど倚くの実務的課題が存圚したす。このセッションでは基瀎的な衛星枬䜍技術を理解し、粟床向䞊の可胜性ず察策を探りたした。 ポむント: 環境䟝存課題: ビル街でのマルチパス、トンネル䞋での衛星ロストなど、ロケヌションごずの特殊芁件が粟床を巊右。 マルチGNSS掻甚: GPS単独でなくGLONASS、Galileo、BeiDou、みちびき(QZSS)など耇数システムを組み合わせお粟床底䞊げ。 ハむブリッド手法: 加速床・ゞャむロセンサ、Wi-Fi/Bluetoothビヌコン、マップマッチングなど補完技術で粟床改善。 基瀎知識が指針に: こうした理解が将来のプロダクトデザむンや品質保蚌、デヌタ分析を行う際の指針ずなる。 4. 埌凊理で䜍眮情報を補正する技術を詊しおみた仮 登壇者: 株匏䌚瀟Luup IoTチヌム 高原健茔さん リアルタむムでの高粟床枬䜍が困難な堎合、埌から粟床を匕き䞊げる「PPKPost-Processing Kinematic」ずいう遞択肢がありたす。高䟡なRTK装眮や特別な通信むンフラを甚いず、取埗枈みデヌタず基準局デヌタを組み合わせお埌凊理する手法です。 ポむント: PPKのメリット: リアルタむムにこだわらず、埌日粟床向䞊が可胜。初期投資を抑えながらセンチメヌトル玚粟床を最終的に実珟。 コスト効率ず拡匵性: 将来的に需芁が増すシナリオで埌から粟床改善を行える柔軟性。配送ロボット、ドロヌン、シェアモビリティなどで有効。 応甚範囲: 地図敎備、走行ログ高床化、むンフラ怜査など、事埌分析が䞭心の領域で倧きな䟡倀を発揮。 https://speakerdeck.com/kensuketakahara/hou-chu-li-dewei-zhi-qing-bao-wobu-zheng-suruji-shu-woshi-sitemita 5. オンデマンドバスサヌビス導入前のシミュレヌションロゞックの構築仮 登壇者: トペタコネクティッド株匏䌚瀟 先行䌁画郚 新技術開発宀 Halufyハルフィさん オンデマンド亀通は柔軟性が魅力ですが、収益性や持続可胜性を確保するのは容易ではありたせん。このセッションでは事前シミュレヌションによる粟緻な需芁予枬や運行蚈画蚭蚈が玹介されたした。 ポむント: 持続可胜なモデル構築: 補助金頌みにならずに最適なステヌション配眮、運行台数、時間垯蚭定をデヌタで怜蚌。 戊略的デヌタ掻甚: 䜍眮情報を䞭心にODデヌタや予玄垌望を統合し、需芁予枬や䟡栌戊略、ルヌト最適化を詊行。 長期的ビゞョン: 他のモビリティ手段やむンフラず連携し、郜垂党䜓の亀通効率化や利䟿性向䞊を目指す土台ずなる。 今埌扱いたいトピックずMobility Nightの展望 今回のMobility Night #1は、GPS・䜍眮情報に特化するこずで、モビリティ業界の「珟圚地把握」技術に深く切り蟌みたした。参加者からは「䜍眮情報ずいう身近なテヌマがこんなに奥深いずは」「基瀎から先端掻甚たで通しお聞けるのは貎重」ずいう声が倚数寄せられおいたす。 しかし、モビリティ業界にはGPS・䜍眮情報以倖にも倚くのテヌマが存圚したす。今埌は、 IoTデバむス掻甚: センサヌからのリアルタむムデヌタ収集・制埡 デヌタ分析: 需芁予枬や高床なオペレヌション最適化 プロダクトデザむン: UX向䞊やナヌザヌ満足床最倧化 品質保蚌: 信頌性確保や安党基準遵守 ずいった領域も掘り䞋げ、業界党䜓のむノベヌションを促す堎にしおいきたいず考えおいたす。 Mobility Nightは、運営メンバヌが䌁画するだけでなく、参加者からの登壇垌望やテヌマ提案も歓迎しおいたす。Discordを通じお意芋亀換や共催募集が可胜な環境を敎え、誰もが関わりやすいコミュニティを目指したす。 https://discord.com/invite/nn7QW5pn8B たずめ 「Mobility Night #1」では、GPS・䜍眮情報技術を軞に、モビリティサヌビスの䞭栞をなす技術的課題ず、その克服による新たな䟡倀創造の可胜性が明確になりたした。静的な地図を動的な情報基盀ぞアップデヌトする詊み、環境に巊右されるGPS粟床を高床な手法で補正する取り組み、オンデマンド亀通をデヌタ駆動型で蚈画する戊略など、倚様なアプロヌチが亀錯したした。 これらの知芋は、今埌のMobility Nightで扱うIoT、デヌタ分析、プロダクトデザむン、品質保蚌などのテヌマずも結び぀き、業界党䜓の進歩を加速させるはずです。匕き続きMobility Nightにご泚目いただき、ずもに孊び、亀流し、新たな䟡倀を創造しおいきたしょう
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の10日目の蚘事です🎅🎄 「Mobility Night」は、モビリティ領域の゜フトりェア技術者、ビゞネスパヌ゜ン、研究者、プロダクトマネヌゞャヌなどが気軜に集たり、業界特有の知芋や課題を共有する勉匷䌚シリヌズです。#0初回のクロヌズド開催を経お、いよいよ第1回#1をオヌプンな圢で開催するこずができたした。 今回のテヌマは、モビリティサヌビスの基盀技術である「GPS・䜍眮情報」。カヌナビや地図アプリ、オンデマンド亀通、そしお将来の自動運転やスマヌトシティ基盀たで、「いたどこにいるか」を正確に把握し、掻甚するこずはサヌビス䟡倀の根幹を支えたす。 圓日は以䞋の5぀のセッションが行われ、GPS・䜍眮情報技術を軞に、それぞれが独自の切り口から課題ず可胜性を瀺したした。 この蚘事は技術広報グルヌプでMobility Nightの䌁画運営も行っおいる䞭西が執筆しおいたす。 1. Exploring New Google Places API 登壇者: KINTOテクノロゞヌズ株匏䌚瀟 numaさん Google Places APIは地図プラットフォヌムの䞭栞機胜の䞀぀であり、呚蟺怜玢や斜蚭情報取埗を効率的に行うための重芁なむンタヌフェヌスです。このセッションでは、テキスト入力䞭から即時に候補を提瀺するAutocomplete機胜の匷化や、Fieldsパラメヌタによる必芁情報の絞り蟌みなど、最新の改善点が玹介されたした。 ポむント: パフォヌマンスずコスト最適化: Fields指定で䞍芁なデヌタ取埗を削枛し、APIコストを抑えるずずもにレスポンス高速化が可胜。 ナヌザヌ゚クスペリ゚ンス向䞊: 欲しい情報に玠早くアクセスできる䜓隓は、移動䞭のナヌザヌにずっお倧きな利点。Autocomplete匷化で怜玢負荷を軜枛し、UXを磚き䞊げる。 将来ぞの展望: 珟圚は䜍眮情報取埗が䞭心だが、将来的にはIoTセンサヌや行動履歎分析ず組み合わせたパヌ゜ナラむズ戊略も期埅できる。 https://speakerdeck.com/kintotechdev/exploring-new-google-places-api 2. AIドラレコサヌビスの走行デヌタで䜜る䜍眮情報デヌタプロダクト 登壇者: GO株匏䌚瀟 束浊慎平さん ドラむブレコヌダヌは事故蚘録甚のデバむスずいう印象が匷いですが、このセッションでは「走行デヌタ街をセンサヌ化するプラットフォヌム」ずしお再解釈されたした。映像GPSデヌタをAI解析するこずで、道路暙識や信号、舗装工事などの情報を動的に地図ぞ反映できる可胜性が瀺唆されたした。 ポむント: ダむナミックな地図曎新: 静的だった地図を“生きた情報基盀”ぞ進化させ、道路むンフラ倉化をほがリアルタむムで反映。 耇数車䞡デヌタの統合: 異なる車䞡から埗られるデヌタを突き合わせるこずで、䞀時的な暙識や工事箇所などを高粟床に怜出。 プラむバシヌ察策: 個人情報が映り蟌む映像を適切に匿名化し぀぀道路情報を保持する技術・運甚が必須。 将来的応甚: 自動運転甚HDマップ敎備、スマヌトシティ蚈画、新サヌビス創出など、倚面的なビゞネス展開が期埅。 https://speakerdeck.com/pemugi/aidorarekosabisunozou-xing-deta-dezuo-ruwei-zhi-qing-bao-detapurodakuto-wei-zhi-qing-bao-jing-du-xiang-shang-nogong-fu 3. GPSモゞュヌルを觊っお孊ぶ、衛星枬䜍技術の抂芁 登壇者: チャリチャリ株匏䌚瀟 VP of Engineering 蛭田慎也さん GPSは圓たり前に利甚されおいたすが、郜垂環境では電波反射や芖界䞍良、衛星数の偏りなど倚くの実務的課題が存圚したす。このセッションでは基瀎的な衛星枬䜍技術を理解し、粟床向䞊の可胜性ず察策を探りたした。 ポむント: 環境䟝存課題: ビル街でのマルチパス、トンネル䞋での衛星ロストなど、ロケヌションごずの特殊芁件が粟床を巊右。 マルチGNSS掻甚: GPS単独でなくGLONASS、Galileo、BeiDou、みちびき(QZSS)など耇数システムを組み合わせお粟床底䞊げ。 ハむブリッド手法: 加速床・ゞャむロセンサ、Wi-Fi/Bluetoothビヌコン、マップマッチングなど補完技術で粟床改善。 基瀎知識が指針に: こうした理解が将来のプロダクトデザむンや品質保蚌、デヌタ分析を行う際の指針ずなる。 4. 埌凊理で䜍眮情報を補正する技術を詊しおみた仮 登壇者: 株匏䌚瀟Luup IoTチヌム 高原健茔さん リアルタむムでの高粟床枬䜍が困難な堎合、埌から粟床を匕き䞊げる「PPKPost-Processing Kinematic」ずいう遞択肢がありたす。高䟡なRTK装眮や特別な通信むンフラを甚いず、取埗枈みデヌタず基準局デヌタを組み合わせお埌凊理する手法です。 ポむント: PPKのメリット: リアルタむムにこだわらず、埌日粟床向䞊が可胜。初期投資を抑えながらセンチメヌトル玚粟床を最終的に実珟。 コスト効率ず拡匵性: 将来的に需芁が増すシナリオで埌から粟床改善を行える柔軟性。配送ロボット、ドロヌン、シェアモビリティなどで有効。 応甚範囲: 地図敎備、走行ログ高床化、むンフラ怜査など、事埌分析が䞭心の領域で倧きな䟡倀を発揮。 https://speakerdeck.com/kensuketakahara/hou-chu-li-dewei-zhi-qing-bao-wobu-zheng-suruji-shu-woshi-sitemita 5. オンデマンドバスサヌビス導入前のシミュレヌションロゞックの構築仮 登壇者: トペタコネクティッド株匏䌚瀟 先行䌁画郚 新技術開発宀 Halufyハルフィさん オンデマンド亀通は柔軟性が魅力ですが、収益性や持続可胜性を確保するのは容易ではありたせん。このセッションでは事前シミュレヌションによる粟緻な需芁予枬や運行蚈画蚭蚈が玹介されたした。 ポむント: 持続可胜なモデル構築: 補助金頌みにならずに最適なステヌション配眮、運行台数、時間垯蚭定をデヌタで怜蚌。 戊略的デヌタ掻甚: 䜍眮情報を䞭心にODデヌタや予玄垌望を統合し、需芁予枬や䟡栌戊略、ルヌト最適化を詊行。 長期的ビゞョン: 他のモビリティ手段やむンフラず連携し、郜垂党䜓の亀通効率化や利䟿性向䞊を目指す土台ずなる。 今埌扱いたいトピックずMobility Nightの展望 今回のMobility Night #1は、GPS・䜍眮情報に特化するこずで、モビリティ業界の「珟圚地把握」技術に深く切り蟌みたした。参加者からは「䜍眮情報ずいう身近なテヌマがこんなに奥深いずは」「基瀎から先端掻甚たで通しお聞けるのは貎重」ずいう声が倚数寄せられおいたす。 しかし、モビリティ業界にはGPS・䜍眮情報以倖にも倚くのテヌマが存圚したす。今埌は、 IoTデバむス掻甚: センサヌからのリアルタむムデヌタ収集・制埡 デヌタ分析: 需芁予枬や高床なオペレヌション最適化 プロダクトデザむン: UX向䞊やナヌザヌ満足床最倧化 品質保蚌: 信頌性確保や安党基準遵守 ずいった領域も掘り䞋げ、業界党䜓のむノベヌションを促す堎にしおいきたいず考えおいたす。 Mobility Nightは、運営メンバヌが䌁画するだけでなく、参加者からの登壇垌望やテヌマ提案も歓迎しおいたす。Discordを通じお意芋亀換や共催募集が可胜な環境を敎え、誰もが関わりやすいコミュニティを目指したす。 https://discord.com/invite/nn7QW5pn8B たずめ 「Mobility Night #1」では、GPS・䜍眮情報技術を軞に、モビリティサヌビスの䞭栞をなす技術的課題ず、その克服による新たな䟡倀創造の可胜性が明確になりたした。静的な地図を動的な情報基盀ぞアップデヌトする詊み、環境に巊右されるGPS粟床を高床な手法で補正する取り組み、オンデマンド亀通をデヌタ駆動型で蚈画する戊略など、倚様なアプロヌチが亀錯したした。 これらの知芋は、今埌のMobility Nightで扱うIoT、デヌタ分析、プロダクトデザむン、品質保蚌などのテヌマずも結び぀き、業界党䜓の進歩を加速させるはずです。匕き続きMobility Nightにご泚目いただき、ずもに孊び、亀流し、新たな䟡倀を創造しおいきたしょう
Introduction Hello, we are Chang and Hosaka, in charge of the my route by KINTO iOS app development in the Mobile App Development Group. In our mobile app development group, we usually use GitHub Actions as a CI/CD tool. This time, we introduced Bitrise for the first time to the my route by KINTO iOS app, so we would like to talk about it. What is Bitrise ? Bitrise is a cloud-based CI/CD (continuous integration / continuous delivery) service for the automated building, testing, and deployment of mobile apps. Bitrise is designed to streamline mobile app development, and it supports major mobile app development frameworks such as iOS, Android, React Native, and Flutter. Some of the key features of Bitrise include: Build Automation Builds are automatically triggered when code in the repository is updated. Builds can be easily configured using the visual interface. Test Automation Tests are automatically run after the builds are complete. Bitrise supports integration with a variety of testing tools, allowing automation at different test levels, including unit testing and UI testing. Automated Deployment If the test passes, Bitrise will automatically take steps to deploy the app. Bitrise supports deployment to app stores such as the App Store and Google Play. Variety of Integration Bitrise supports integration with a variety of tools and services, including GitHub, Bitbucket, Slack, Jira, Firebase, and more. Cloud-based Services Bitrise is a cloud-based service that does not require infrastructure configuration or management. Developers can easily take advantage of Bitrise’s features. Bitrise is a powerful tool for streamlining and improving the quality of mobile app development, and is a very valuable CI/CD service to developers and development teams. The Reason for Introducing Bitrise There are two reasons why we implemented Bitrise in the my route by KINTO iOS app. We had an opportunity to hear from Bitrise Ltd. before building a CI/CD environment, and at that time all the team members had replaced their PCs from Intel to M1, so we were fascinated by Bitrise, which can be built in the same M1 environment. The results of the experiment below, comparing Bitrise and Github Actions on an Intel Medium machine (the lowest performance), show that the cost can be reduced by about 30% and the processing time can be shortened by about 50%. Bitrise and GitHub Actions performance comparison experiment (tested in a different app): Processing Time Comparison Experimental Attempt / Machine Name Bitrise (Intel Medium) Github Actions 1 07:48 16:24 2 11:42 16:18 3 06:53 16:09 Average 08:48 16:17 Cost Comparison Github Actions cost per minute is $0.08 Bitrise cost per minute is $0.106666 Bitrise calculation: Given that 1 credit (cr.) = elapsed minutes (min.) × machine spec (2)... (i), and $400/7,500 credit = apprrx. 0.05333($/cr)... (ii), For (i) and (ii), apprx. 0.05333($/cr.) × 2(cr./min.) = apprx. 0.106666 ($/min.) Experimental Count / Machine Name Bitrise (Intel Medium) Github Actions 1 0.85 1.36 2 1.28 1.36 3 0.75 1.36 Average 0.96 1.36 Using Bitrise in my route by KINTO To implement Bitrise in my route KINTO, we signed up for Bitrise's Teams pricing plan and adopted an M1 Medium machine consuming 2 credits per minute. The Teams plan has a credit limit set according to the price, and exceeding that limit incurs additional costs, so we aimed to optimize costs by also using GitHub Actions. With GitHub Actions, Linux is 1/10 the cost of macOS . Therefore, we use GitHub Actions for steps that can run on Linux (no app build required) and Bitrise for steps that require macOS (app build required). Bitrise Workflow my route by KINTO, mainly automates the following: unit testing, deployment to App Store and TestFlight, and build result notifications to Slack. Currently, builds are triggered by pushing to the develop and release branches, and scheduled builds are done on weekday mornings. We have observed that a single build takes about 6-11 minutes (12-22 credits). GitHub Actions Workflow GitHub Actions automates the static analysis flow. SwiftLint: A static analysis tool for Swift that automatically points out any code violations in the PR. SonarQube: A static analysis tool that analyzes code duplication and other issues that SwiftLint cannot cover. Summary and Future Prospects Looking ahead, Bitrise is expected to continue to expand and improve its features to meet the needs of mobile app development. For example, we can expect more advanced testing and deployment options, more flexible workflow settings, and further expansion of cloud-based resources. It is also expected to provide a more seamless development experience, including collaboration with the developer community and improved integration with other tools. KINTO Technologies would like to keep a close eye on the trends and lead to further utilization of this technology. Here is the review. https://findy-tools.io/products/bitrise/18/39
Introduction My name is PannNuWai and I work in the Global Development Group at KINTO Technologies. In Global Development Group test automation team, I build and maintain test automation environments for the product development teams, and write test scripts with the product test team. In my previous company, I was involved in testing, but after joining KINTO Technologies, I had my first experience with automation testing using Appium, which provided me with valuable learning opportunities. I didn’t have any experience in Appium, so I had to start studying from scratch. However, I am now capable of handling everything from initial configuration to designing server architecture. In this automation testing role, I primarily focused on testing smartphone apps and will outline the issues I resolved during the process. In this article, I would like to talk about how to switch to dark mode using Appium version 1.22.3 for automation testing. What is automation testing? Software testing is the process of identifying issues in software to ensure that defective products are not released. In this article, 'automation testing' refers to the use of tools that support and automate the software testing process. Benefits of automation testing [^1] Early fault detection Improve quality while keeping costs down Tests can be performed even with a lack of human resources Tests can be performed more quickly Human error can be eliminated Tests can be performed outside of business hours [^1]: https://products.sint.co.jp/obpm/blog/test_automation What is Appium? It is an open source tool for testing native, web views, and hybrid apps on iOS, Android, and desktop platforms. [^2] [^2]: https://appium.io Appium supports Java, PHP, and Python programming languages, so it is an automation testing tool that testers can easily use while choosing their preferred language. There are three components: Appium Client Appium Server End Device in the architecture of Appium. The mobile device and app details are set up in the Appium Client. The Appium Server uses Node.js language to connect the simulator (iOS) or emulator (Android) while launching the json file. Finally, the end device is managed through the Appium server that has been launched. What is Appium Inspector? Appium Inspector is a standard procedure for uniquely identifying the UI elements of a mobile app. It works on both actual devices or simulators (iOS) or emulators (Android). Note - the Appium Inspector tool is specifically designed to retrieve only native mobile application attributes, so it does not support finding locators in a web browser (Chrome). The Appium desktop application is a combination of the Appium server itself and the Element inspector, designed to detect all visible elements of mobile applications while developing test scripts. [^3] [^3]: https://www.kobiton.com/book/chapter-5-the-appium-inspector-2 What is Dark Mode? Dark mode is a display setting for the user interface of smartphones, laptops, etc. Instead of displaying dark text (dark mode) on a bright screen, light text (light mode) is displayed on a black screen. In addition to the existing dark mode feature on both Android and iOS phones, we often use the dark mode feature in our apps. When testing mobile app automation, testing the dark mode feature was also a key checking point. So, I would like to talk about the dark mode of mobile apps using Appium. Problem There is a problem when using Appium to test dark mode. For example, when testing the login screen to see if the characters of username and password are displayed, the Appium inspector retrieves the location of the element for username and password . Normally, you only need to check that the element is displayed as follows. AssertTrue(driver.findElementByXPath("USER_NAME").isDisplayed()); AssertTrue(driver.findElementByXPath("PASSWORD").isDisplayed()); However, in dark mode, it is not enough to just retrieve the location of the element and check its display. Checking that the screen has changed to black is an important part of dark mode. You need to check the hexadecimal values for the black and white colors. Test Method Now, let’s actually write the source code of the dark mode test case using Appium. changeToDarkTheme Step 1 Retrieve the location (ElementId) of the element from the Appium inspector. Step 2 Use assertElementColorMode(MobileElement elementId, ColorMode colorMode) to confirm if light mode is the Default setting. Step 3 Press the dark mode button. Step 4 Use assertElementColorMode(MobileElement elementId, ColorMode colorMode) to confirm if the Display setting changes to dark mode. public class DisplayChangePage extends Base { public static final String THEME_CELL_ID = "id/theme_parent"; @Test(groups = "DisplayChangePage", dependsOnGroups = "Setting") public void changeToDarkTheme() { driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS); MobileElement themeCell = getDriver().findElementById(THEME_CELL_ID); assertElementColorMode(themeCell, ColorMode.LIGHT); themeCell.click(); driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS); tapElement( findElementByTextContains(ViewType.CHECKED_TEXT, resourceText("darkTheme")) ); themeCell = getDriver().findElementById(THEME_CELL_ID); assertElementColorMode(themeCell, ColorMode.DARK); } } assertElementColorMode Set the ElementId where the Theme cell is located and the ColorMode you want to change as parameters. Step 1 Retrieve evidence of the Element where the Theme cell is located. Use getElementBufferedImage(MobileElement element) to save the evidence as an image file. Step 2 Check that the saved image file does not become null . Step 3 Get the color from the (x = 10, y = 10) image file point and check the color of the dark mode you want to change. public interface AppiumHelpersInterface extends FindElementsInterface { AppiumDriver<MobileElement> getDriver(); Device getDevice(); /** * Get buffered image of mobile element * * @param element Mobile element * @return Buffered image */ default BufferedImage getElementBufferedImage(MobileElement element) { File image = element.getScreenshotAs(OutputType.FILE); try { return ImageIO.read(image); } catch (IOException e) { return null; } } /** * Assert element's color mode * * @param element Mobile Element * @param mode Color mode */ default void assertElementColorMode(MobileElement element, ColorMode mode) { BufferedImage image = getElementBufferedImage(element); Assert.assertNotNull(image); Assert.assertTrue(Utils.getColorString(image, 10, 10).matches(mode.cellRegex())); } } getColorString Change the color of x-point and y-point of the acquired image to hexadecimal and return the array. /** * Get color string from image at point x and y * * @param image BufferedImage * @param x int * @param y int * @return Hexadecimal Color String */ public static String getColorString(BufferedImage image, int x, int y) { int rgba = image.getRGB(x, y); int[] rgb = new int[]{ (rgba >> 16) & 0xff, (rgba >> 8) & 0xff, (rgba) & 0xff }; return String.format("%02x%02x%02x", rgb[0], rgb[1], rgb[2]); } cellRegex Determine the values for dark and light modes. public enum ColorMode { LIGHT, DARK; public String cellRegex() { // 22222 - lighter black if (this == DARK) return "2[(0-9|a-f)]2[(0-9|a-f)]2[(0-9|a-f)]"; // ffffff return "f[(0-9|a-f)]f[(0-9|a-f)]f[(0-9|a-f)]"; } } public interface ColorModeInterface { String darkModeScript(); Map<String, Object> darkModeSettings(); Map<String, Object> lightModeSettings(); default void configureDarkMode(ColorMode mode) { getDriver().executeScript(darkModeScript(), mode == ColorMode.DARK ? darkModeSettings() : lightModeSettings()); } } Caution This case involves using Appium, so only the native app's dark mode feature can be utilized. Summary In this article, I have explained how to switch to dark mode. It can be used on both iOS (version 13 and up) and Android (version 5.0 and up) for dark mode automation testing. This time, I tested the Native App's dark mode feature, but I would also like to explore testing the Web App's dark mode feature in the future Since December, the number of members on the test automation team in Global Development has increased. In the future, I hope to collaborate with team members on automation testing using not only Appium but also other tools like Katalon. Reference DarkMode Appium Architecture
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の9日目の蚘事です🎅🎄 匊瀟KINTOテクノロゞヌズは、300人を超える゚ンゞニア䞭心の組織です。耇雑化する事業環境の䞭で、私たちは垞に組織の効率性ず創造性のバランスを暡玢し続けおいたす。拠点の分散やリモヌトワヌクの増加により、郚門を超えたコミュニケヌションは限定的なものずなり、この課題に真剣に向き合う必芁がありたした。 この蚘事では日々のコミュニケヌションで利甚しおいるSlackを軞にどのように組織を掻性化するのか、そしおSlackをベヌスにタレントサヌチを構築しおどのようなこずを実珟しようずしおいるのか、その取組の第䞀歩をご玹介すべく技術広報グルヌプの䞭西が曞いおいたす。 タレントサヌチずは瀟員のスキル情報をデヌタ化しお怜玢できる仕組みのこずです なぜタレントサヌチが必芁だったのか 効率的に業務をこなすこずは倧切ですが、むノベヌションを生み出すためには、偶然の出䌚いや「無駄」に思える䌚話が実は重芁な圹割を果たしたす。私たちの組織では、日々の業務に集䞭するあたり、「暗黙知」や「朜圚的な可胜性」を芋萜ずしがちでした。 䟋えば、「このスキルを持぀人はいないだろうか」ず思っおも、誰に盞談すればいいか分からない。組織の芏暡が倧きくなるに぀れ、こうした情報の非察称性は深刻な課題ずなっおいたした。そこで私たちは、Slackプロフィヌルを戊略的に掻甚し、この課題に正面から取り組むこずにしたのです。 Slackプロフィヌル掻甚のメリット 技術広報グルヌプの芖点 これたで目立たなかった人材の発芋 組織内には、その才胜や可胜性に気づかれおいない人材が倚く存圚したす。技術広報グルヌプのメンバヌは日々瀟内の皆さんずコミュニケヌションを取っおおりたすが、それでも党おの瀟員の皆さんを深く知るこずは難しく、Slackプロフィヌルは、そうした「隠れた人材」を可芖化する新しい手段ずなっおいきたす。 プロゞェクト支揎の迅速化 適切なスキルを持぀人材を玠早く特定できるこずで、プロゞェクトの立ち䞊げや課題解決のスピヌドが劇的に向䞊するこずを期埅しおいたす。今たでは、〇〇ずいうスキルを持っおいる人居ないですかなどず瀟内でも口䌝おで探し回ったりしおいたすが我々のような組織のハブずなる組織を介さずにコミュニケヌションが取れるネットワヌクを構築しおいくこずは今埌の組織の成長にずっおずおも重芁なこずです 郚眲間のコラボレヌション促進 これたで技術広報グルヌプでは、瀟内の勉匷䌚や亀流むベント、瀟倖講垫をお招きしおの勉匷䌚など、様々な䌁画を実斜し、それたで接点のなかった郚眲間での぀ながりを䜜り続けるこずで瀟内でも自然にコミュニケヌションが発生し、䞀床繋がったずころから数珠぀なぎにネットワヌクが構築され、日々新たなコラボレヌションが生たれおいたす。今回のSlack斜策もそれに拍車をかけおいくこずでしょう 党瀟員にずっおのメリット キャリア成長の機䌚拡倧 自分のスキルや興味を明確に衚珟するこずで、これたで気づかなかった新たな可胜性が開かれたす。自分では思わぬキヌワヌドで぀ながるこずで、草の根で様々な機䌚が生たれおきたす。これは単に業務に限らず、共通の悩みを持぀方々がお互いに孊んで成長する機䌚が生たれおきたす。 スキルを持぀同僚ぞの玠早いアクセス 具䜓的には、新入瀟員が「Next.jsに詳しいフロント゚ンド゚ンゞニア」を探す際、Slack䞊ですぐに簡単に怜玢できるようになり、孊習や課題解決における倧きな助けずなっおいきたす。Slack䞊でメッセヌゞを怜玢する延長線䞊に瀟内のタレントデヌタベヌスが構築されお怜玢できるようになりたす。 自然な瀟内亀流の掻性化 瀟内には様々な趣味の草の根掻動も存圚しおいたす。それが趣味ず呌べるかどうかは別にしお興味領域ごずに、腰の健康に関するチャンネルから簡単に䜜れるレシピをシェアするチャンネル。各皮スポヌツやゲヌム、もちろん新しい技術に関するチャンネルもありたすし、これらに個人がより玐づけやすくなっおきお、業務以倖での぀ながりがあるこずで、業務で発生した緊急時の察応でもスムヌズに執り行えるずいうこずもありたす。 プロフィヌル䜜成をサポヌトする仕組み 「䜕を曞けばいいか分からない」ずいう声に応えるため、技術広報グルヌプが積極的にサポヌトしおいたす。このアプロヌチは、単なる情報収集ではなく、瀟員䞀人ひずりの可胜性を匕き出すための䞁寧な察話プロセスです。 匊瀟はテックブログを開始した圓初より瀟員の皆さんの才胜を芋぀け出せるようにむンタビュヌを実斜させおいただいたり、䌎走しながら蚘事の執筆や登壇資料の䜜成、むベント䌁画や運営、勉匷䌚の運営サポヌトなど行っおいたす。今回のプロフィヌル䜜成に関しおも、この蚘事を読んでいる瀟員の方でただプロフィヌルを埋めおいないずいう方や、䜕を曞いたらよいかわからないずいう方はぜひお声がけください。䞀緒にあなたの魅力を芋぀けお瀟内で発信しおいきたしょう サポヌト内容 個別ヒアリングによる経隓や興味の匕き出し 1察1の察話を通じお、本人も気づいおいない朜圚的な匷みを探りたす。 プロフィヌル䜜成甚のテンプレヌト 自己衚珟が苊手な方でも、安心しお蚘入できたす。 自己衚珟が苊手な方ぞの蚀語化支揎 専門スタッフが寄り添いながら、自分の匷みや興味を適切に衚珟する手䌝いをしたす。 テンプレヌト 怜玢結果 今埌の展望 珟圚は手動でのタレントサヌチですが、将来的にはAIを掻甚したスキルマッチングシステムの構築を目指しおいたす。蓄積されたデヌタを効果的に掻甚し、より効率的で戊略的な人材掻甚の実珟を芖野に入れおいたす。将来的に個々の瀟員の可胜性をさらに深く理解し、最適な機䌚ず結び぀けるこずができるでしょう。 おわりに Slackプロフィヌルは、単なる自己玹介欄ではありたせん。それは、人ず人ずを結び぀ける組織の朜圚胜力を匕き出すための戊略的なツヌルであり、䞀人ひずりの可胜性を解攟する鍵なのです。 あなたの興味、スキル、可胜性を積極的に発信するこずで、組織党䜓の可胜性を広げるこずができたす。私たちは、この小さな䞀歩が、やがお倧きな倉革に぀ながるず信じおいたす。
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の9日目の蚘事です🎅🎄 はじめに こんにちは、DX開発グルヌプでアプリケヌション゚ンゞニアを行っおいる亀山です。 近幎、生成AIはさたざたな分野で掻甚されおおり、私たちの開発チヌムでも生成AIを掻甚したシステムの構築が行われおいたす。 さらに、私たちの開発チヌム内で広く䜿甚されおいるJavaは、既存の知芋やツヌルを掻かしながら生成AIのむンタヌフェヌスを効率的に構築するこずができるず考えたした。本蚘事では、こうした背景を螏たえ、Javaを甚いお生成AIを呌び出し、結果を凊理する方法に぀いお解説したす。 今回はその導入線ずしお、Javaコヌドを甚いおAzure OpenAIのラむブラリを甚いお、呌び出す基本的な実装に぀いお、シンプルなコヌド䟋を甚いおお話したいず思いたす。Azure OpenAIはOpenAIに比べ、高いスケヌラビリティや信頌性に優れ、芏暡の倧きい業務システムずも芪和性が高いプラットフォヌムずされおいたす。 Azure Open AIのセットアップ Azure サブスクリプションに登録したす。 https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account?icid=ai-services&azure-portal=true あずは䞋蚘ペヌゞの説明に埓っお゚ンドポむントずキヌを取埗したす。 https://learn.microsoft.com/ja-jp/azure/ai-services/openai/chatgpt-quickstart?tabs=command-line%2Cjavascript-keyless%2Ctypescript-keyless%2Cpython-new&pivots=programming-language-java 取埗先のAzureコン゜ヌルは こちら になりたす。 ※先ほど登録したアカりントでのログむンが必芁なペヌゞになりたす。 OpenAIラむブラリのセットアップ 今回Azure OpenAIをコヌルするにあたり、AzureのSDKラむブラリを䜿甚したす。 Azure OpenAIではこのSDKラむブラリによっお簡単に生成AIを呌び出すためのコヌディングを行うこずができたす。 Gradleの堎合 dependencies { implementation 'com.azure:azure-ai-openai:1.0.0-beta.12' implementation 'com.azure:azure-core-http-okhttp:1.7.8' } Mavenの堎合 <dependencies> <dependency> <groupId>com.azure</groupId> <artifactId>azure-ai-openai</artifactId> <version>1.0.0-beta.12</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core-http-okhttp</artifactId> <version>1.7.8</version> </dependency> </dependencies> 䜿甚バヌゞョンは執筆時点のものであるため、最新バヌゞョン぀いおは公匏ドキュメントでご確認ください。 特に今回䜿甚するAzure OpenAI client library for Javaは珟圚ベヌタ版であるため、今埌安定版がリリヌスされた堎合にはそちらを䜿甚しおいただくこずをおすすめしたす。 実際にAzure OpenAIのチャットモデルを呌び出しおみる 参考: https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/openai/azure-ai-openai /src/main/resource/config.properties endpoint=https://{リ゜ヌス名}.openai.azure.com/ apiKey={APIキヌ} 取埗したAzure OpenAIの゚ンドポむントずAPIキヌを別ファむルで管理するためこちらに入力しおください。 シヌクレット情報の管理方法はご自身たたはチヌムの方針に合わせおいただいおも倧䞈倫です。 /src/main/java/com/sample/app/Main.java package com.sample.app; // ご自身のパッケヌゞ名に合わせおください import com.azure.ai.openai.OpenAIClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.ai.openai.models.*; import com.azure.core.credential.AzureKeyCredential; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // プロパティファむルを読み蟌む ※別の方法でキヌ情報を管理する堎合は適宜倉曎しおください Properties properties = new Properties(); try (InputStream input = Main.class.getClassLoader().getResourceAsStream("config.properties")) { if (input == null) { System.out.println("config.properties ファむルが芋぀かりたせん。"); return; } properties.load(input); } catch (IOException ex) { System.out.println(ex.getMessage()); return; } // プロパティから蚭定倀を取埗 String endpoint = properties.getProperty("endpoint"); String apiKey = properties.getProperty("apiKey"); // OpenAIクラむアントを䜜成 var client = new OpenAIClientBuilder() .endpoint(endpoint) .credential(new AzureKeyCredential(apiKey)) .buildClient(); // プロンプトを準備 List<ChatRequestMessage> messages = new ArrayList<>() .setTemperature(0.7) // 応答のランダム性、高いほど倚様0.0~2.0 .setMaxTokens(100) // 応答の最倧トヌクン数 .setFrequencyPenalty(0.0) // 頻出する単語に察するペナルティ-2.0~2.0 .setPresencePenalty(0.6); // 既存のトピックに関連させるものに察するペナルティ-2.0~2.0 messages.add(new ChatRequestSystemMessage("あなたは優秀なAIアシスタントです。")); messages.add(new ChatRequestUserMessage("初心者向けに、Javaのクラスずオブゞェクトの違いを説明しおください。")); // リク゚ストオプションを蚭定 var options = new ChatCompletionsOptions(messages); var chatCompletions = client.getChatCompletions("gpt-4o", options); // 䜿甚するデプロむ名たたは生成AIモデル名を指定しおください // リク゚ストを送信しお結果を取埗 for (ChatChoice choice : chatCompletions.getChoices()) { ChatResponseMessage message = choice.getMessage(); System.out.printf("Index: %d, Chat Role: %s.%n", choice.getIndex(), message.getRole()); System.out.println("Message:"); System.out.println(message.getContent()); } } } 生成AIの呌び出しにあたり、様々なパラメヌタを蚭定するこずができたす。普段䜿甚するChatGPTアプリケヌションでは珟圚蚭定できないパラメヌタで、こういったパラメヌタを調敎できるのもプログラムから生成AIを呌び出すメリットの1぀です。今回は4぀のパラメヌタtemperature、maxTokens、frequencyPenalty、presencePenaltyを蚭定したしたが、他にも様々なパラメヌタがありたす。詳现は こちら を参照しおください。 たた、messagesの郚分で䞋蚘2皮類のメッセヌゞをセットしたした。前者のChatRequestSystemMessageはセットしなくおも実行可胜です。 ChatRequestSystemMessage 生成AIモデルの振る舞いや圹割を蚭定するためのメッセヌゞで、䌚話のトヌンや応答の仕方を定矩したす。 ChatRequestUserMessage ナヌザヌからの具䜓的な質問や指瀺をAIに䌝えるためのメッセヌゞで、このメッセヌゞに察する回答が返华倀ずしおOpenAIから返されたす。 getChatCompletionsの第1匕数はデプロむ名たたはモデル名を入力したす。 デプロむ名はAzureポヌタルで取埗したす。Azure以倖のOpenAIを䜿甚する堎合はモデル名「gpt-4o」「gpt-3.5-turbo」などを入力したす䞊蚘の䟋だずモデル名を入力しおいたす。 .gitignore /src/main/resources/config.properties 今回ご説明するように/src/main/resource/config.propertiesで管理する堎合は.gitignoreに䞊蚘1行を远加しおください。 特にリポゞトリで管理する際はAPIキヌなどのシヌクレット情報の取り扱いにはくれぐれもご泚意ください 実行結果 䞋蚘のようなレスポンスをOpenAIから埗るこずができたした。実際にはMarkdown圢匏の文字列です Index: 0, Chat Role: assistant. Message: Javaにおけるクラスずオブゞェクトの違いは、初めおプログラミングを孊ぶ人にずっお重芁な抂念です。以䞋に分かりやすく説明したす。 クラス 蚭蚈図 : クラスはオブゞェクトを䜜成するための蚭蚈図やテンプレヌトず考えるこずができたす。クラスにはオブゞェクトの属性フィヌルドや動䜜メ゜ッドが定矩されおいたす。 宣蚀 : Javaではクラスを定矩するために`class`キヌワヌドを䜿甚したす。䟋えば、車を衚すクラスは以䞋のように定矩できたす。 public class Car { // フィヌルド属性 String color; int year; // メ゜ッド動䜜 void drive() { System.out.println("The car is driving"); } } オブゞェクト むンスタンス : オブゞェクトは、クラスから生成された実䜓むンスタンスです。オブゞェクトは特定のデヌタを持ち、そのデヌタに察する操䜜を行うこずができたす。 生成 : Javaでは`new`キヌワヌドを䜿甚しおクラスからオブゞェクトを生成したす。䟋えば、`Car`クラスからオブゞェクトを䜜成する堎合は次のようになりたす。 public class Main { public static void main(String[] args) { // Carクラスのむンスタンスを䜜成 Car myCar = new Car(); myCar.color = "Red"; myCar.year = 2020; // オブゞェクトのメ゜ッドを呌び出す myCar.drive(); } } たずめ クラス はオブゞェクトを䜜成するためのテンプレヌトであり、属性や動䜜を定矩しおいたす。 オブゞェクト はそのクラスの実際のむンスタンスであり、具䜓的なデヌタを持ち、定矩された動䜜を実行できたす。 この基本的な関係を理解するこずで、より耇雑なプログラム構築を始めるこずができたす。 終わりに 今回の蚘事では、JavaでAzure OpenAIを利甚する基本的な方法に぀いお玹介したした。 JavaによるOpenAIの利甚に関する情報はただ少ないため、本蚘事が皆様のお圹に立おれば幞いです。 次回はより高床な掻甚方法に぀いお解説しおいきたいず思っおおりたすので、匕き続きよろしくお願いいたしたす。
Hello everyone, this is Martin from the Mobile Development Group here at KINTO Technologies! With this guide I hope to give you a quick overview on how to build your TFLite (TensorFlow Lite) models from scratch so let's dive straight into it. This article is the entry for December 9th in the KINTO Technologies Advent Calendar 2024 🎅🎄 Preparation There are basically two ways to prepare your dataset. One is to do the annotation process locally and the other is to annotate your dataset online whilst collaborating and sharing the initial workload better with your team members. This guide tries to emphasize the use of Roboflow ( https://roboflow.com/ ). Roboflow's model export functionality allows you to export trained models in various formats, making it easy to deploy them in your own applications or further fine-tune them. In our case we want to train TFlite models so we would want to export to the TFRecord format as shown in the image below. However, in case you are not using any third party online annotation tools such as Roboflow to annotate your images online and you want to annotate locally, you could try out the free Python library labelImg: https://github.com/HumanSignal/labelImg In general, either locally or online, first we need to collect the dataset of images and label them to get the corresponding bounding box classification meta data (xml) files. (in our case creating the VOC [Visual Object Classes] Pascal meta data) more information about Pascal VOC can be found here: https://roboflow.com/formats/pascal-voc-xml After creating a Google Cloud Platform standard runtime instance you will need to connect it to your Colab notebook. In essence, Google Colab provides a secure, scalable, and collaborative platform for data science and machine learning teams within organizations. Once that is done we first need to import the necessary libraries to get Tensorflow going (Step 1) Creation of GCP standard instance: Creation of Colab Enterprise ( https://cloud.google.com/colab/docs ) notebook: Connect your Google Cloud bucket (Step 4) Execution Install the TensorFlow Object Detection API (Step 5 in this guide) Generate the TFRecord files required for training. (need generate_tfrecord.py script to produce csv files for this) Edit the model pipeline config file and download the pre-trained model checkpoint Train and evaluate the model Export and convert the model into TFlite(TensorFlow Lite) format Deployment Deploy the TFlite model on Android / iOS / IoT devices So now get's started Here are the steps that you should undergo within your Colab Enterprise notebook in detail: 1) Import Libraries !pip install tensorflow==2.13.0 import os import glob import xml.etree.ElementTree as ET import pandas as pd import tensorflow as tf print(tf.__version__) * 2) Create customTF2 , training and data folders in your GCP cloud storage bucket (Necessary only the first time) * Create a folder named customTF2 in your GCP cloud storage bucket Create two sub-folders called training and data inside the customTF2 folder (The training folder is where the checkpoints will be saved during training) Creation of folder structure in your GCS bucket: 3) Download, save and upload the following as generate_tfrecord.py file to the customTF2 folder to your bucket. (Necessary only for the first time) from __future__ import division from __future__ import print_function from __future__ import absolute_import import os import io import pandas as pd import tensorflow as tf import argparse from PIL import Image from tqdm import tqdm from object_detection.utils import dataset_util from collections import namedtuple, OrderedDict def __split(df, group): data = namedtuple('data', ['filename', 'object']) gb = df.groupby(group) return [data(filename, gb.get_group(x)) for filename, x in zip(gb.groups.keys(), gb.groups)] def create_tf_example(group, path, class_dict): with tf.io.gfile.GFile(os.path.join(path, '{}'.format(group.filename)), 'rb') as fid: encoded_jpg = fid.read() encoded_jpg_io = io.BytesIO(encoded_jpg) image = Image.open(encoded_jpg_io) width, height = image.size filename = group.filename.encode('utf8') image_format = b'jpg' xmins = [] xmaxs = [] ymins = [] ymaxs = [] classes_text = [] classes = [] for index, row in group.object.iterrows(): if set(['xmin_rel', 'xmax_rel', 'ymin_rel', 'ymax_rel']).issubset(set(row.index)): xmin = row['xmin_rel'] xmax = row['xmax_rel'] ymin = row['ymin_rel'] ymax = row['ymax_rel'] elif set(['xmin', 'xmax', 'ymin', 'ymax']).issubset(set(row.index)): xmin = row['xmin'] / width xmax = row['xmax'] / width ymin = row['ymin'] / height ymax = row['ymax'] / height xmins.append(xmin) xmaxs.append(xmax) ymins.append(ymin) ymaxs.append(ymax) classes_text.append(str(row['class']).encode('utf8')) classes.append(class_dict[str(row['class'])]) tf_example = tf.train.Example(features=tf.train.Features( feature={ 'image/height': dataset_util.int64_feature(height), 'image/width': dataset_util.int64_feature(width), 'image/filename': dataset_util.bytes_feature(filename), 'image/source_id': dataset_util.bytes_feature(filename), 'image/encoded': dataset_util.bytes_feature(encoded_jpg), 'image/format': dataset_util.bytes_feature(image_format), 'image/object/bbox/xmin': dataset_util.float_list_feature(xmins), 'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs), 'image/object/bbox/ymin': dataset_util.float_list_feature(ymins), 'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs), 'image/object/class/text': dataset_util.bytes_list_feature(classes_text), 'image/object/class/label': dataset_util.int64_list_feature(classes), })) return tf_example def class_dict_from_pbtxt(pbtxt_path): # open file, strip \n, trim lines and keep only # lines beginning with id or display_name with open(pbtxt_path, 'r', encoding='utf-8-sig') as f: data = f.readlines() name_key = None if any('display_name:' in s for s in data): name_key = 'display_name:' elif any('name:' in s for s in data): name_key = 'name:' if name_key is None: raise ValueError( "label map does not have class names, provided by values with the 'display_name' or 'name' keys in the contents of the file" ) data = [l.rstrip('\n').strip() for l in data if 'id:' in l or name_key in l] ids = [int(l.replace('id:', '')) for l in data if l.startswith('id')] names = [ l.replace(name_key, '').replace('"', '').replace("'", '').strip() for l in data if l.startswith(name_key)] # join ids and display_names into a single dictionary class_dict = {} for i in range(len(ids)): class_dict[names[i]] = ids[i] return class_dict if __name__ == '__main__': parser = argparse.ArgumentParser( description='Create a TFRecord file for use with the TensorFlow Object Detection API.', formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('csv_input', metavar='csv_input', type=str, help='Path to the CSV input') parser.add_argument('pbtxt_input', metavar='pbtxt_input', type=str, help='Path to a pbtxt file containing class ids and display names') parser.add_argument('image_dir', metavar='image_dir', type=str, help='Path to the directory containing all images') parser.add_argument('output_path', metavar='output_path', type=str, help='Path to output TFRecord') args = parser.parse_args() class_dict = class_dict_from_pbtxt(args.pbtxt_input) writer = tf.compat.v1.python_io.TFRecordWriter(args.output_path) path = os.path.join(args.image_dir) examples = pd.read_csv(args.csv_input) grouped = __split(examples, 'filename') for group in tqdm(grouped, desc='groups'): tf_example = create_tf_example(group, path, class_dict) writer.write(tf_example.SerializeToString()) writer.close() output_path = os.path.join(os.getcwd(), args.output_path) print('Successfully created the TFRecords: {}'.format(output_path)) 4) Mount your GCS bucket, install GCSFUSE and link your folder from google.colab import auth auth.authenticate_user() !echo "deb https://packages.cloud.google.com/apt gcsfuse-bionic main" > /etc/apt/sources.list.d/gcsfuse.list !curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - !apt -qq update !apt -qq install gcsfuse !gsutil ls -r gs://your-cloud-storage-bucket-name !mkdir customTF2 !gcsfuse --implicit-dirs your-cloud-storage-bucket-name customTF2 5) Clone the tensorflow models git repository & Install TensorFlow Object Detection API %cd /content # clone the tensorflow models on the colab cloud vm !git clone --q https://github.com/tensorflow/models.git #navigate to /models/research folder to compile protos %cd models/research # Compile protos. !protoc object_detection/protos/*.proto --python_out=. # Install TensorFlow Object Detection API. !cp object_detection/packages/tf2/setup.py . !python -m pip install . 6) Test the model builder (Suggested) %cd /content/models/research # testing the model builder !pip install 'tf-models-official >=2.5.1, <2.16.0' !python object_detection/builders/model_builder_tf2_test.py 7) Download pre-trained model checkpoint (Necessary only for the first time) Current working directory is /content/customTF2/customTF2/data/ Download ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz into the data folder & unzip it. A list of detection checkpoints for other tensorflow 2.x can be found here . %cd /content/customTF2/customTF2/data/ #Download the pre-trained model ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz into the data folder & unzip it. !wget http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz !tar -xzvf ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz 8) Get the model pipeline config file, make changes to it and put it inside the data folder (Necessary every time and when you change the amount of class numbers) Current working directory is /content/customTF2/customTF2/data/ Download ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config from /content/models/research/object_detection/configs/tf2 . Make the required changes to it and upload it to the /content/customTF2/customTF2/data/ folder. OR Edit the config file from /content/models/research/object_detection/configs/tf2 in colab and copy the edited config file to the /content/customTF2/customTF2/data folder. You can also find the pipeline config file inside the model checkpoint folder we just downloaded in the previous step. You need to make the following changes: change num_classes to the number of your classes change test.record path, train.record path & labelmap path to the paths where you have created these files (paths should be relative to your current working directory while training) change fine_tune_checkpoint to the path of the directory where the downloaded checkpoint from step 12 is change fine_tune_checkpoint_type with value classification or detection depending on your classification type change batch_size to any multiple of 8 depending upon the capability of your GPU (eg:- 24,128,...,512) - usually 24 for smaller datasets and 32 for larger datasets works well with a standard colab enterprise instance change num_steps to number of steps you want the detector to train. #copy the edited config file from the configs/tf2 directory to the data/ folder in your GCP storage !cp /content/models/research/object_detection/configs/tf2/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config /content/customTF2/customTF2/data In the next step we want to make use of the official TensorBoard tool to visualize our runs and graphs to inspect learning and classification loss over time. More information on how to read graphs and how to use the tool can be found here: https://www.tensorflow.org/tensorboard/get_started#:~:text=TensorBoard%20is%20a%20tool%20for,during%20the%20machine%20learning%20workflow . 9) Load TensorBoard (Recommended) # cload tensorboard %cd /content/customTF2/customTF2/training # !pip install tensorboard # tensorboard --inspect --logdir /content/customTF2/customTF2/training # !gcloud init # !gcloud auth application-default login %reload_ext tensorboard %tensorboard --logdir '/content/customTF2/customTF2/training' 10) Train the model Navigate to the object_detection folder in colab vm %cd /content/models/research/object_detection 10 (a) Training using model_main_tf2.py (Suggested method) Here {PIPELINE_CONFIG_PATH} points to the pipeline config and {MODEL_DIR} points to the directory in which training checkpoints and events will be written. For best results, you should stop the training when the loss is less than 0.1 if possible, else train the model until the loss does not show any significant change for a while. The ideal loss should be below 0.05 (Try to get the loss as low as possible without overfitting the model. Don’t go too high on training steps to try and lower the loss if the model has already converged viz. if it does not reduce loss significantly any further and takes a while to go down. ) !pip install tensorflow==2.13.0 # Run the command below from the content/models/research/object_detection directory """ PIPELINE_CONFIG_PATH=path/to/pipeline.config MODEL_DIR=path to training checkpoints directory NUM_TRAIN_STEPS=50000 SAMPLE_1_OF_N_EVAL_EXAMPLES=1 python model_main_tf2.py -- \ --model_dir=$MODEL_DIR --num_train_steps=$NUM_TRAIN_STEPS \ --sample_1_of_n_eval_examples=$SAMPLE_1_OF_N_EVAL_EXAMPLES \ --pipeline_config_path=$PIPELINE_CONFIG_PATH \ --alsologtostderr """ !python model_main_tf2.py --pipeline_config_path=/content/customTF2/customTF2/data/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config --model_dir=/content/customTF2/customTF2/training --alsologtostderr 10 (b) Evaluation using model_main_tf2.py (Optional, just if you want more customization) You can run this in parallel by opening another colab notebook and running this command simultaneously along with the training command above (don't forget to mount your gcp storage, clone the TF git repo and install the TF2 object detection API there as well). This will give you validation loss, mAP, etc so you have a better idea of how your model is performing. Here {CHECKPOINT_DIR} points to the directory with checkpoints produced by the training job. Evaluation events are written to {MODEL_DIR/eval} . # Run the command below from the content/models/research/object_detection directory """ PIPELINE_CONFIG_PATH=path/to/pipeline.config MODEL_DIR=path to training checkpoints directory CHECKPOINT_DIR=${MODEL_DIR} NUM_TRAIN_STEPS=50000 SAMPLE_1_OF_N_EVAL_EXAMPLES=1 python model_main_tf2.py -- \ --model_dir=$MODEL_DIR --num_train_steps=$NUM_TRAIN_STEPS \ --checkpoint_dir=${CHECKPOINT_DIR} \ --sample_1_of_n_eval_examples=$SAMPLE_1_OF_N_EVAL_EXAMPLES \ --pipeline_config_path=$PIPELINE_CONFIG_PATH \ --alsologtostderr """ !python model_main_tf2.py --pipeline_config_path=/content/customTF2/customTF2/data/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config --model_dir=/content/customTF2/customTF2/training/ --checkpoint_dir=/content/customTF2/customTF2/training/ --alsologtostderr Retraining your model (In case you get disconnected) If you get disconnected or lose your session on colab vm, you can start your training where you left off as the checkpoint is saved on your cloud storage inside the training folder. To restart the training simply run steps 1, 4, 5, 6, 9 and 10. Note that since we have all the files required for training like the record files, our edited pipeline config file, the label_map file and the model checkpoint folder, we do not need to create these again. The model_main_tf2.py script saves the checkpoint every 1000 steps. The training automatically restarts from the last saved checkpoint itself. However, if you see that it doesn't restart training from the last checkpoint you can make 1 change in the pipeline config file. Change fine_tune_checkpoint to where your latest trained checkpoints have been written and have it point to the latest checkpoint as shown below: fine_tune_checkpoint: "/content/customTF2/customTF2/training/ckpt-X" (where ckpt-X is the latest checkpoint) 11) Test your trained model Export inference graph Current working directory is /content/models/research/object_detection %cd /content/models/research/object_detection !pip install tensorflow==2.13.0 ##Export inference graph !python exporter_main_v2.py --trained_checkpoint_dir=/content/customTF2/customTF2/training --pipeline_config_path=/content/customTF2/customTF2/data/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config --output_directory /content/customTF2/customTF2/data/inference_graph Test your trained Object Detection model on images (Provide test image of your liking and adjust image_path) Current working directory is /content/models/research/object_detection %cd /content/models/research/object_detection # Different font-type for labels text.(This step is optional) !wget https://www.freefontspro.com/d/14454/arial.zip !unzip arial.zip -d . %cd utils/ !sed -i "s/font = ImageFont.truetype('arial.ttf', 24)/font = ImageFont.truetype('arial.ttf', 50)/" visualization_utils.py %cd .. %cd /content/models/research/object_detection !pip install tensorflow=="2.12.0" #Loading the saved_model import tensorflow as tf import time import numpy as np import warnings warnings.filterwarnings('ignore') from PIL import Image from google.colab.patches import cv2_imshow from object_detection.utils import label_map_util from object_detection.utils import visualization_utils as viz_utils IMAGE_SIZE = (12, 8) # Output display size as you want import matplotlib.pyplot as plt PATH_TO_SAVED_MODEL="/content/customTF2/customTF2/data/inference_graph/saved_model" print('Loading model...', end='') # Load saved model and build the detection function detect_fn=tf.saved_model.load(PATH_TO_SAVED_MODEL) print('Done!') #Loading the label_map category_index=label_map_util.create_category_index_from_labelmap("/content/customTF2/customTF2/data/label_map.pbtxt",use_display_name=True) def load_image_into_numpy_array(path): return np.array(Image.open(path)) # Replace with your test image image_path = "/content/customTF2/customTF2/data/images/your_test.jpg" #print('Running inference for {}... '.format(image_path), end='') image_np = load_image_into_numpy_array(image_path) # The input needs to be a tensor, convert it using `tf.convert_to_tensor`. input_tensor = tf.convert_to_tensor(image_np) # The model expects a batch of images, so add an axis with `tf.newaxis`. input_tensor = input_tensor[tf.newaxis, ...] detections = detect_fn(input_tensor) # All outputs are batches tensors. # Convert to numpy arrays, and take index [0] to remove the batch dimension. # We're only interested in the first num_detections. num_detections = int(detections.pop('num_detections')) detections = {key: value[0, :num_detections].numpy() for key, value in detections.items()} detections['num_detections'] = num_detections # detection_classes should be ints. detections['detection_classes'] = detections['detection_classes'].astype(np.int64) image_np_with_detections = image_np.copy() viz_utils.visualize_boxes_and_labels_on_image_array( image_np_with_detections, detections['detection_boxes'], detections['detection_classes'], detections['detection_scores'], category_index, use_normalized_coordinates=True, max_boxes_to_draw=200, min_score_thresh=.8, # Adjust this value to set the minimum probability boxes to be classified as True agnostic_mode=False) %matplotlib inline plt.figure(figsize=IMAGE_SIZE, dpi=200) plt.axis("off") plt.imshow(image_np_with_detections) plt.show() Converting trained SSD (Single Shot Detector) model to TFLite model 12) Install tf-nightly TFLite converter works better with tf-nightly. %cd /content/models/research/object_detection !pip install tensorflow=="2.12.0" !pip install numpy==1.26.4 !pip install tf-nightly 13) Export SSD TFLite graph Current working directory is /content/models/research/object_detection # !pip3 uninstall keras # !pip3 install keras==2.14.0 !pip3 install --upgrade tensorflow keras !pip3 install tensorflow=="2.12.0" # !pip3 install --upgrade tensorflow keras # !pip3 install tensorflow=="2.13.1" # !pip3 install numpy --upgrade # !pip3 uninstall numpy # !pip3 install numpy=="1.22.0" # !pip3 install tensorflow --upgrade #!python --version %cd /content/models/research/object_detection !python export_tflite_graph_tf2.py --pipeline_config_path /content/customTF2/customTF2/data/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.config --trained_checkpoint_dir /content/customTF2/customTF2/training --output_directory /content/customTF2/customTF2/data/tflite 14) Convert TF saved model to TFLite model Current working directory is /mydrive/customTF2/data/ %cd /content/customTF2/customTF2/data/ Check input and output tensor names !saved_model_cli show --dir /content/customTF2/customTF2/data/tflite/saved_model --tag_set serve --all Converting to TFlite: Use either Method (a) or Method (b). METHOD (a) Using command-line tool tflite_convert - (Basic model conversion) # The default inference type is Floating-point. %cd /content/customTF2/customTF2/data/ !tflite_convert --saved_model_dir=tflite/saved_model --output_file=tflite/detect.tflite METHOD (b) Using Python API - (For advanced model conversion with optimizations etc) %cd /mydrive/customTF2/data/ #'''******************************** # FOR FLOATING-POINT INFERENCE #*********************************''' #import tensorflow as tf saved_model_dir = '/content/customTF2/customTF2/data/tflite/saved_model' #converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) #tflite_model = converter.convert() #open("/content/customTF2/customTF2/data/tflite/detect.tflite", "wb").write(tflite_model) #'''************************************************** # FOR FLOATING-POINT INFERENCE WITH OPTIMIZATIONS #***************************************************''' import tensorflow as tf converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir,signature_keys=['serving_default']) converter.optimizations = [tf.lite.Optimize.DEFAULT] converter.experimental_new_converter = True converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS, tf.lite.OpsSet.SELECT_TF_OPS] tflite_model = converter.convert() with tf.io.gfile.GFile('/mydrive/customTF2/data/tflite/detect.tflite', 'wb') as f: f.write(tflite_model) #'''********************************** # FOR DYNAMIC RANGE QUANTIZATION #************************************* # The model is now a bit smaller with quantized weights, but other variable data is still in float format.''' # import tensorflow as tf # converter = tf.lite.TFLiteConverter.from_saved_model('/content/customTF2/customTF2/data/tflite/saved_model',signature_keys=['serving_default']) # converter.optimizations = [tf.lite.Optimize.DEFAULT] # tflite_quant_model = converter.convert() # with tf.io.gfile.GFile('/content/customTF2/customTF2/data/tflite/detect.tflite', 'wb') as f: # f.write(tflite_quant_model) # '''*********************************************************************** # FOR INTEGER WITH FLOAT FALLBACK QUANTIZATION WITH DEFAULT OPTMIZATIONS # ************************************************************************** # Now all weights and variable data are quantized, and the model is significantly smaller compared to the original TensorFlow Lite model. # However, to maintain compatibility with applications that traditionally use float model input and output tensors, # the TensorFlow Lite Converter leaves the model input and output tensors in float''' # import tensorflow as tf # import numpy as np # saved_model_dir = '/content/customTF2/customTF2/data/tflite/saved_model' # def representative_dataset(): # for _ in range(100): # data = np.random.rand(1, 320, 320, 3) # yield [data.astype(np.float32)] # converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) # converter.optimizations = [tf.lite.Optimize.DEFAULT] # converter.representative_dataset = representative_dataset # tflite_quant_model = converter.convert() # with open('/content/customTF2/customTF2/data/tflite/detect.tflite', 'wb') as f: # f.write(tflite_quant_model) # '''********************************* # FOR FULL INTEGER QUANTIZATION # ************************************ # The internal quantization remains the same as previous float fallback quantization method, # but you can see the input and output tensors here are also now integer format''' # import tensorflow as tf # import numpy as np # saved_model_dir = '/content/customTF2/customTF2/data/tflite/saved_model' # def representative_dataset(): # for _ in range(100): # data = np.random.rand(1, 320, 320, 3) # yield [data.astype(np.float32)] # converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) # converter.optimizations = [tf.lite.Optimize.DEFAULT] # converter.representative_dataset = representative_dataset # converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] # converter.inference_input_type = tf.uint8 # converter.inference_output_type = tf.uint8 # tflite_quant_model_full_int = converter.convert() # with open('/content/customTF2/customTF2/data/tflite/detect.tflite', 'wb') as f: # f.write(tflite_quant_model_full_int) Read more about post-training quantization here . You can also read about these in this colab notebook. 15) Create TFLite metadata !pip install tflite_support_nightly %cd /content/customTF2/customTF2/data/ %cd tflite/ !mkdir tflite_with_metadata %cd .. Create a labelmap.txt file with the names of the classes written in each line inside the data folder. Finally run the following cell to create the detect.tflite model with metadata attached to it. Current working directory is /content/customTF2/customTF2/data/ %cd /content/customTF2/customTF2/data/ !pip uninstall tensorflow !pip install tensorflow=="2.13.1" # Attach Metadata to TFLite from tflite_support.metadata_writers import object_detector from tflite_support.metadata_writers import writer_utils import flatbuffers import platform from tensorflow_lite_support.metadata import metadata_schema_py_generated from tensorflow_lite_support.metadata import schema_py_generated from tensorflow_lite_support.metadata.python import metadata from tensorflow_lite_support.metadata.python import metadata_writers import flatbuffers import os from tensorflow_lite_support.metadata import metadata_schema_py_generated as _metadata_fb from tensorflow_lite_support.metadata.python import metadata as _metadata from tensorflow_lite_support.metadata.python.metadata_writers import metadata_info from tensorflow_lite_support.metadata.python.metadata_writers import metadata_writer from tensorflow_lite_support.metadata.python.metadata_writers import writer_utils ObjectDetectorWriter = object_detector.MetadataWriter _MODEL_PATH = "/content/customTF2/customTF2/data/tflite/detect.tflite" _LABEL_FILE = "/content/customTF2/customTF2/data/labelmap.txt" _SAVE_TO_PATH = "/content/customTF2/customTF2/data/tflite/tflite_with_metadata/detect.tflite" writer = ObjectDetectorWriter.create_for_inference( writer_utils.load_file(_MODEL_PATH), [127.5], [127.5], [_LABEL_FILE]) writer_utils.save_file(writer.populate(), _SAVE_TO_PATH) # Verify the populated metadata and associated files. displayer = metadata.MetadataDisplayer.with_model_file(_SAVE_TO_PATH) print("Metadata populated:") print(displayer.get_metadata_json()) print("Associated file(s) populated:") print(displayer.get_packed_associated_file_list()) model_meta = _metadata_fb.ModelMetadataT() model_meta.name = "SSD_Detector" model_meta.description = ( "Identify which of a known set of objects might be present and provide " "information about their positions within the given image or a video " "stream.") # Creates input info. input_meta = _metadata_fb.TensorMetadataT() input_meta.name = "image" input_meta.content = _metadata_fb.ContentT() input_meta.content.contentProperties = _metadata_fb.ImagePropertiesT() input_meta.content.contentProperties.colorSpace = ( _metadata_fb.ColorSpaceType.RGB) input_meta.content.contentPropertiesType = ( _metadata_fb.ContentProperties.ImageProperties) input_normalization = _metadata_fb.ProcessUnitT() input_normalization.optionsType = ( _metadata_fb.ProcessUnitOptions.NormalizationOptions) input_normalization.options = _metadata_fb.NormalizationOptionsT() input_normalization.options.mean = [127.5] input_normalization.options.std = [127.5] input_meta.processUnits = [input_normalization] input_stats = _metadata_fb.StatsT() input_stats.max = [255] input_stats.min = [0] input_meta.stats = input_stats # Creates outputs info. output_location_meta = _metadata_fb.TensorMetadataT() output_location_meta.name = "location" output_location_meta.description = "The locations of the detected boxes." output_location_meta.content = _metadata_fb.ContentT() output_location_meta.content.contentPropertiesType = ( _metadata_fb.ContentProperties.BoundingBoxProperties) output_location_meta.content.contentProperties = ( _metadata_fb.BoundingBoxPropertiesT()) output_location_meta.content.contentProperties.index = [1, 0, 3, 2] output_location_meta.content.contentProperties.type = ( _metadata_fb.BoundingBoxType.BOUNDARIES) output_location_meta.content.contentProperties.coordinateType = ( _metadata_fb.CoordinateType.RATIO) output_location_meta.content.range = _metadata_fb.ValueRangeT() output_location_meta.content.range.min = 2 output_location_meta.content.range.max = 2 output_class_meta = _metadata_fb.TensorMetadataT() output_class_meta.name = "category" output_class_meta.description = "The categories of the detected boxes." output_class_meta.content = _metadata_fb.ContentT() output_class_meta.content.contentPropertiesType = ( _metadata_fb.ContentProperties.FeatureProperties) output_class_meta.content.contentProperties = ( _metadata_fb.FeaturePropertiesT()) output_class_meta.content.range = _metadata_fb.ValueRangeT() output_class_meta.content.range.min = 2 output_class_meta.content.range.max = 2 label_file = _metadata_fb.AssociatedFileT() label_file.name = os.path.basename("labelmap.txt") label_file.description = "Label of objects that this model can recognize." label_file.type = _metadata_fb.AssociatedFileType.TENSOR_VALUE_LABELS output_class_meta.associatedFiles = [label_file] output_score_meta = _metadata_fb.TensorMetadataT() output_score_meta.name = "score" output_score_meta.description = "The scores of the detected boxes." output_score_meta.content = _metadata_fb.ContentT() output_score_meta.content.contentPropertiesType = ( _metadata_fb.ContentProperties.FeatureProperties) output_score_meta.content.contentProperties = ( _metadata_fb.FeaturePropertiesT()) output_score_meta.content.range = _metadata_fb.ValueRangeT() output_score_meta.content.range.min = 2 output_score_meta.content.range.max = 2 output_number_meta = _metadata_fb.TensorMetadataT() output_number_meta.name = "number of detections" output_number_meta.description = "The number of the detected boxes." output_number_meta.content = _metadata_fb.ContentT() output_number_meta.content.contentPropertiesType = ( _metadata_fb.ContentProperties.FeatureProperties) output_number_meta.content.contentProperties = ( _metadata_fb.FeaturePropertiesT()) # Creates subgraph info. group = _metadata_fb.TensorGroupT() group.name = "detection result" group.tensorNames = [ output_location_meta.name, output_class_meta.name, output_score_meta.name ] subgraph = _metadata_fb.SubGraphMetadataT() subgraph.inputTensorMetadata = [input_meta] subgraph.outputTensorMetadata = [ output_location_meta, output_class_meta, output_score_meta, output_number_meta ] subgraph.outputTensorGroups = [group] model_meta.subgraphMetadata = [subgraph] b = flatbuffers.Builder(0) b.Finish( model_meta.Pack(b), _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER) metadata_buf = b.Output() When asked, proceed with 'Y' 16) Download the TFLite model Congrats, you are done! Final thoughts Google Colab Enterprise is a powerful cloud-based platform for machine learning, making it an ideal environment for building TensorFlow Lite models. After over a year of using this platform, I've found that the most time-consuming part of the process is data preparation and the initial trial-and-error phase. This stage requires significant iteration and testing to identify challenges in recognizing specific parts of the dataset and to address false positives, where images are incorrectly classified. *The Android robot header image was reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の9日目の蚘事です🎅🎄 匊瀟KINTOテクノロゞヌズは、300人を超える゚ンゞニア䞭心の組織です。耇雑化する事業環境の䞭で、私たちは垞に組織の効率性ず創造性のバランスを暡玢し続けおいたす。拠点の分散やリモヌトワヌクの増加により、郚門を超えたコミュニケヌションは限定的なものずなり、この課題に真剣に向き合う必芁がありたした。 この蚘事では日々のコミュニケヌションで利甚しおいるSlackを軞にどのように組織を掻性化するのか、そしおSlackをベヌスにタレントサヌチを構築しおどのようなこずを実珟しようずしおいるのか、その取組の第䞀歩をご玹介すべく技術広報グルヌプの䞭西が曞いおいたす。 タレントサヌチずは瀟員のスキル情報をデヌタ化しお怜玢できる仕組みのこずです なぜタレントサヌチが必芁だったのか 効率的に業務をこなすこずは倧切ですが、むノベヌションを生み出すためには、偶然の出䌚いや「無駄」に思える䌚話が実は重芁な圹割を果たしたす。私たちの組織では、日々の業務に集䞭するあたり、「暗黙知」や「朜圚的な可胜性」を芋萜ずしがちでした。 䟋えば、「このスキルを持぀人はいないだろうか」ず思っおも、誰に盞談すればいいか分からない。組織の芏暡が倧きくなるに぀れ、こうした情報の非察称性は深刻な課題ずなっおいたした。そこで私たちは、Slackプロフィヌルを戊略的に掻甚し、この課題に正面から取り組むこずにしたのです。 Slackプロフィヌル掻甚のメリット 技術広報グルヌプの芖点 これたで目立たなかった人材の発芋 組織内には、その才胜や可胜性に気づかれおいない人材が倚く存圚したす。技術広報グルヌプのメンバヌは日々瀟内の皆さんずコミュニケヌションを取っおおりたすが、それでも党おの瀟員の皆さんを深く知るこずは難しく、Slackプロフィヌルは、そうした「隠れた人材」を可芖化する新しい手段ずなっおいきたす。 プロゞェクト支揎の迅速化 適切なスキルを持぀人材を玠早く特定できるこずで、プロゞェクトの立ち䞊げや課題解決のスピヌドが劇的に向䞊するこずを期埅しおいたす。今たでは、〇〇ずいうスキルを持っおいる人居ないですかなどず瀟内でも口䌝おで探し回ったりしおいたすが我々のような組織のハブずなる組織を介さずにコミュニケヌションが取れるネットワヌクを構築しおいくこずは今埌の組織の成長にずっおずおも重芁なこずです 郚眲間のコラボレヌション促進 これたで技術広報グルヌプでは、瀟内の勉匷䌚や亀流むベント、瀟倖講垫をお招きしおの勉匷䌚など、様々な䌁画を実斜し、それたで接点のなかった郚眲間での぀ながりを䜜り続けるこずで瀟内でも自然にコミュニケヌションが発生し、䞀床繋がったずころから数珠぀なぎにネットワヌクが構築され、日々新たなコラボレヌションが生たれおいたす。今回のSlack斜策もそれに拍車をかけおいくこずでしょう 党瀟員にずっおのメリット キャリア成長の機䌚拡倧 自分のスキルや興味を明確に衚珟するこずで、これたで気づかなかった新たな可胜性が開かれたす。自分では思わぬキヌワヌドで぀ながるこずで、草の根で様々な機䌚が生たれおきたす。これは単に業務に限らず、共通の悩みを持぀方々がお互いに孊んで成長する機䌚が生たれおきたす。 スキルを持぀同僚ぞの玠早いアクセス 具䜓的には、新入瀟員が「Next.jsに詳しいフロント゚ンド゚ンゞニア」を探す際、Slack䞊ですぐに簡単に怜玢できるようになり、孊習や課題解決における倧きな助けずなっおいきたす。Slack䞊でメッセヌゞを怜玢する延長線䞊に瀟内のタレントデヌタベヌスが構築されお怜玢できるようになりたす。 自然な瀟内亀流の掻性化 瀟内には様々な趣味の草の根掻動も存圚しおいたす。それが趣味ず呌べるかどうかは別にしお興味領域ごずに、腰の健康に関するチャンネルから簡単に䜜れるレシピをシェアするチャンネル。各皮スポヌツやゲヌム、もちろん新しい技術に関するチャンネルもありたすし、これらに個人がより玐づけやすくなっおきお、業務以倖での぀ながりがあるこずで、業務で発生した緊急時の察応でもスムヌズに執り行えるずいうこずもありたす。 プロフィヌル䜜成をサポヌトする仕組み 「䜕を曞けばいいか分からない」ずいう声に応えるため、技術広報グルヌプが積極的にサポヌトしおいたす。このアプロヌチは、単なる情報収集ではなく、瀟員䞀人ひずりの可胜性を匕き出すための䞁寧な察話プロセスです。 匊瀟はテックブログを開始した圓初より瀟員の皆さんの才胜を芋぀け出せるようにむンタビュヌを実斜させおいただいたり、䌎走しながら蚘事の執筆や登壇資料の䜜成、むベント䌁画や運営、勉匷䌚の運営サポヌトなど行っおいたす。今回のプロフィヌル䜜成に関しおも、この蚘事を読んでいる瀟員の方でただプロフィヌルを埋めおいないずいう方や、䜕を曞いたらよいかわからないずいう方はぜひお声がけください。䞀緒にあなたの魅力を芋぀けお瀟内で発信しおいきたしょう サポヌト内容 個別ヒアリングによる経隓や興味の匕き出し 1察1の察話を通じお、本人も気づいおいない朜圚的な匷みを探りたす。 プロフィヌル䜜成甚のテンプレヌト 自己衚珟が苊手な方でも、安心しお蚘入できたす。 自己衚珟が苊手な方ぞの蚀語化支揎 専門スタッフが寄り添いながら、自分の匷みや興味を適切に衚珟する手䌝いをしたす。 テンプレヌト 怜玢結果 今埌の展望 珟圚は手動でのタレントサヌチですが、将来的にはAIを掻甚したスキルマッチングシステムの構築を目指しおいたす。蓄積されたデヌタを効果的に掻甚し、より効率的で戊略的な人材掻甚の実珟を芖野に入れおいたす。将来的に個々の瀟員の可胜性をさらに深く理解し、最適な機䌚ず結び぀けるこずができるでしょう。 おわりに Slackプロフィヌルは、単なる自己玹介欄ではありたせん。それは、人ず人ずを結び぀ける組織の朜圚胜力を匕き出すための戊略的なツヌルであり、䞀人ひずりの可胜性を解攟する鍵なのです。 あなたの興味、スキル、可胜性を積極的に発信するこずで、組織党䜓の可胜性を広げるこずができたす。私たちは、この小さな䞀歩が、やがお倧きな倉革に぀ながるず信じおいたす。
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の8日目の蚘事です🎅🎄 こんにちは、孊びの道の駅チヌムのHOKAです。 孊びの道の駅が発足されおからそろそろ1幎が経過するので、ちょっずした振り返りBlogを曞いおみたす。 䞀幎前に考えおいたこず 孊びの道の駅チヌムはちょうど1幎前の2023幎11月末に、きんちゃん、䞭西、HOKAの3人が集たっお「このたくさんの勉匷䌚をもっず盛り䞊げおいきたい」ずいうずころからスタヌトしたした。 そこで、私たちは2024幎の幎明けすぐに集たり、むンセプションデッキを䜜りたした。 その結果がこちら↓ 瀟内の「勉匷䌚」ず「勉匷䌚」が亀わる「道の駅」ずしお、勉匷䌚を軞にした瀟内掻性を支揎したす。 瀟内広報掻動 今床、こういうテヌマで勉匷䌚やるよ 気になるあの勉匷䌚、どんな感じなんだろう 勉匷䌚の支揎 勉匷䌚を始めおみたいけど、どうやっお始めるず良いのか 勉匷䌚の運営しおいるけど、盛り䞊がらない  などの、お悩み盞談 詳しくは「 はじたりのテックブログ 」に蚘茉しおおりたす。 はじめの6か月でやったこず たず、月に䞀床開催されるKTCの党瀟MTG通称本郚䌚で孊びの道の駅の掻動を毎月報告したした。 おそらく、䞋蚘のスラむドは瀟内でお銎染みになったかず思いたす。 掻動サマリヌ 勉匷䌚党般の盞談​勉匷䌚を始めたい盞談したい等「勉匷䌚に関するご盞談」を承る堎​ 突撃​ずなりの勉匷䌚事務局が「みなさんの勉匷䌚」を芋孊する掻動​ KTC Podcast​勉匷䌚の運営者にむンタビュヌ音声で空気感ごず瀟内にお届け​ TechBlog​TechBlogで「KTCにこんな勉匷䌚あるよ、参加したよ」を䌝えたす​ 新たな掻動 ヌそのヌ 6か月を過ぎた頃から、私たちの掻動に賛同しおくれた方が珟れ始めたした。 「どんな勉匷䌚があるかを怜玢できるようにしたい」ずいう課題を抱えおいたのですが、モバむルアプリ開発グルヌプの゚ンゞニアがSlackを掻甚した怜玢システムを䜜っおくれたした。 これにより、勉匷䌚怜玢方法は、Slackチャンネルでキャラクタヌたなびぃにメンションするず、勉匷䌚を芋぀けられるこずになったのです。 詳しくは開発者のBlogをご芧ください https://blog.kinto-technologies.com/posts/2024-12-04_manabyi/ 新たな掻動 ヌそのヌ 「勉匷䌚の資料や動画を誰でもい぀でも閲芧できる状態にしたい」ずいう課題もありたした。 そんな䞭、コヌポレヌトITグルヌプの゚ンゞニアが自ら名乗り出お、Sharepointを掻甚した勉匷䌚のポヌタルサむトを䜜っおくれたした。 その名も「孊びの道の駅 Portal」 無味也燥だったフォルダが、たるでYouTubeのようになりたした。 動画や資料がそろったこずにより、 「圓日参加できなくなっおしたった勉匷䌚、資料だけでも芋おおこう」 「先日の勉匷䌚を埩習したいな、動画を芋るか」 ずいったムヌブメントができるようになりたした。 その他、うれしかったこず 勉匷䌚を運営䞭の方から盞談の䟝頌が来るようになったこず。 Podcastの出挔䟝頌をするず、皆さん快諟しおくれるこず。 党瀟むベントのポスタヌに「孊びの道の駅」ずいう蚀葉が䜿われおいたこず。 グルヌプ䌚瀟の瀟内向け資料に、KTCを玹介する文脈で「孊びの道の駅」が玹介されたこず。 ただ1幎も経っおいないのに、瀟内だけでなくグルヌプ䌚瀟たで私たちの掻動が届くようになるずは、1幎前の自分たちでは想像さえしおおりたせんでした。 ふずした時に、瀟員の方から「道の駅、めっちゃ良いね」ず蚀っおもらえるのも倧きな励みずなっおいたす。 技術広報グルヌプにゞョむン そんな孊びの道の駅チヌムですが、2024幎9月から技術広報グルヌプの傘䞋に入るこずになりたした。 技術広報グルヌプずは 2022幎にKINTOテクノロゞヌズ TechBlogを立ち䞊げ、瀟倖に向けたむベントや、瀟内の勉匷䌚の開催など゚ンゞニアがアりトプットをする堎づくりをしおきたした。そもそも業務で成果を出すこずもアりトプットですが、勉匷䌚やTechBlogも゚ンゞニアにずっおは重芁なアりトプットの堎だず私たちは考えおおりたす。技術広報グルヌプでは「業務ず業務の間」ずも蚀えるアりトプットの堎づくりをしおきたした。 2023幎末に私たちの始めた「孊びの道の駅」は、「業務ず業務の間」に必芁ずされる孊びの堎むンプットの堎を䜜っおおり、技術広報グルヌプず非垞に近い存圚だったのです。 ※そもそも技術広報グルヌプ発起人の䞭西は、「むンプットからアりトプットたで䞋支えするこずで゚ンゞニアの成長を促す」ずいう構想だったので、別々のものではなかったのかもしれたせん。 今埌の孊びの道の駅チヌムは ぜひ䞭西によるTechBlogをご芧いただければ幞いです。 https://blog.kinto-technologies.com/posts/2024-12-03-the-next-goal/
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の8日目の蚘事です🎅🎄 はじめに こんにちは。モバむルアプリ開発グルヌプの䞭本です。 普段は、倧阪にいながら東京のメンバヌず協力しお、 KINTO Unlimitedアプリ のiOS開発をしおいたす。 本蚘事では、KINTO Unlimitedアプリ(iOS)のアヌキテクチャ改善の過皋に぀いお詳しく説明したす。 このアプリのアヌキテクチャは、1st → 2nd → 3rdず段階的に進化し、最終的には独自のアヌキテクチャぞず移行したした。 それぞれの段階における蚭蚈や課題に぀いお、以䞋でお話ししたす。 1st Generation Architecture VIPERアヌキテクチャを採甚 すべおの画面をUIKit + xib/storyboardで実装 Combineを䜿甚しおViewを曎新 ファヌストリリヌスに向けお玍期が短かったこずもあり、瀟内で実瞟のあったアヌキテクチャを採甚 1stの蚭蚈 flowchart TD id1(ViewController) -- publish --> id2(ViewModel) -- subscribe --> id1 id2 -- request --> id3(Interactor) id1 -- apply view property --> id4(UIView) id1 -- transition --> id5(Router) ViewController ViewModelにむベントを通知 ViewModelからのむベントに基づいおアりトプットを賌読 賌読結果に応じおViewを曎新し、Routerを呌び出しお画面遷移を実斜 ViewModel Combineを䜿甚しおリアクティブに状態を倉化 むベントPublisherを倉換し、Viewの状態をPublisher経由でアりトプット Interactor APIや内郚DBにリク゚ストを実斜 Router 他の画面ぞの遷移凊理を実斜 UIView コヌド/xib/storyboardを䜿甚しおレむアりト 1stの課題 UIKitを䜿甚したレむアりトは開発コストが高く、特にxib/storyboardを䜿甚した堎合は倉曎が容易ではない → SwiftUIぞ移行したい 2nd Generation Architecture UIKitからSwiftUIぞ移行 UIKitによるレむアりトをSwiftUIに眮き換え、開発効率を改善 UIHostingControllerを䜿甚しおSwiftUIのViewをViewControllerに泚入 画面遷移は埓来通りUIKitで実斜 圓時、SwiftUIの画面遷移APIは䞍安定だったためUIKitのたた SwiftUIぞの移行に専念する 䞀床にたくさん倉曎するず、機胜仕様のデグレが懞念されるため 2ndの蚭蚈 flowchart TD id1(ViewController) -- input --> id2(ViewModel) -- output --> id1 id2 -- request --> id6(Interactor) id1 -- mapping --> id3(ScreenModel) -- publish --> id1 id3 -- publish --> id4(View) -- publish --> id3 id1 -- transit --> id5(Router) ViewController HostingControllerInjectableプロトコルを実装し、SwiftUI Viewを远加 ViewModelのアりトプットを賌読し、ScreenModel(ObservableObject)に反映 ViewModelのアりトプットやScreenModelのPublisherを賌読し、Routerを甚いお画面遷移を実斜 ScreenModel Viewの状態を保持するObservableObject ViewModel / Interactor / Router 1st Generationず同様の機胜 2ndの課題 状態管理がViewModelずScreenModelの䞡方で行われるため、ロゞックが分散し、開発・保守コストが増加 1stからの課題 Combineによるリアクティブな状態倉化の実装は保守性に懞念があり、コヌド量が倚く可読性に難がある 1画面に぀き1぀のViewModelであるため、機胜の倚い画面ではViewModelが巚倧化 → CombineやViewModelから脱华したい 3rd Generation Architecture Combineを甚いたViewModelから状態を集䞭管理するViewStoreを䞭心ずしたアヌキテクチャぞ移行 むベントの結果をAnyPublisherを経ずに盎接ObservableObjectに反映できる仕組みを実珟 Combineを䜿甚せず、async/awaitを甚いおリアクティブな状態倉曎を実珟 状態管理ロゞックを機胜ごずに分割可胜 3rdの蚭蚈 flowchart TD subgraph ViewStore id1(ActionHandler) -- update --> id2(State) end id2 -- bind --> id5(View) -- publish action --> id1 id1 -- publish routing --> id3(ViewController) -- publish action --> id1 id3 -- transit --> id4(Router) id1 -- request --> id6(Interactor) ViewStore State Viewの状態を保持するObservableObjectで、SwiftUIのViewで䜿甚 Action 埓来のViewModelのtransformメ゜ッドにおけるINPUTに盞圓する機胜を提䟛するenum ActionHandler Actionを匕数にずり、Stateを曎新するハンドラヌ async/awaitを䜿甚しお実装 ViewController routerSubjectを賌読し、Routerを甚いお画面遷移を実斜 Interactor / Router 2nd Generationず同様 ActionHandlerの分割 機胜の倚い画面では、ActionHandlerずStateを分割するこずで、コヌドの可読性や保守性を向䞊させるこずができる StateのactionPublisherを他のStateにバむンドするこずで、あるViewから他のViewにアクションを送るこずが可胜 flowchart TD subgraph ViewStore id2 -- action --> id1 id1(ActionHandler1) -- update --> id2(State1) id5 -- action --> id4 id4(ActionHandler2) -- update --> id5(State2) id8 -- action --> id7 id7(ActionHandler3) -- update --> id8(State3) end subgraph Parent View id3 id6 id9 end id2 -- bind --> id3(View1) id5 -- bind --> id6(View2) id8 -- bind --> id9(View3) おわりに 今回の取り組みは、機胜開発ず䞊行しお1幎以䞊かけお進めおきたした。 珟圚では、ほがすべおの゜ヌスコヌドが3rd Generation Architectureに眮き換わっおいたす。 その結果、コヌドの可読性や保守性が向䞊し、今埌の開発が非垞にやりやすくなったず感じおいたす。 匕き続き、さらなる改善を重ねおいければず思いたす
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の12日目の蚘事です🎅🎄 はじめに こんにちは。KINTOテクノロゞヌズ モバむル開発グルヌプ プロデュヌサヌチヌムの やたゆき です。  匊瀟では生成AIの䜿い方を孊ぶ機䌚が、たくさん提䟛されおいたす。  私ぱンゞニアではありたせんが、今回、生成AIでビゞネスサむド向けの運甚ツヌル「お知らせHTML䜜成ツヌル」を開発しおみたした。 この話をするず、よく「え生成AIを組み蟌んだの」ず勘違いをされるのですが、 そうではなく「生成AIを道具ずしお䜿っお開発した」ずいうのがこの蚘事の内容です。  非゚ンゞニアでも、自分で開発しお業務を改善できるそのこずを実感したので、事䟋ずしおご玹介したいず思いたす。 背景やろうず思った理由 私の担圓アプリでは、PUSH通知をタップするず「お知らせの詳现ペヌゞ」が開きたす。このペヌゞはWeb Viewになっおおり、HTMLファむルをWebにアップロヌドするこずでお知らせを衚瀺できたす。 そのため、次のようなステップで運甚されおいたした。 ビゞネスサむドから、衚瀺したい内容をExcel・Word圢匏で入皿 開発サむドの誰かがコヌディングしおHTMLファむルを䜜成 ビゞネスサむドでHTMLファむルをチェック 開発サむドの誰かが修正 [堎合によっおをくり返し] Web反映  入皿は、週に〜回のペヌス。 入皿元のビゞネスサむドでは、入皿時に手元で完成圢を確認できないため、䜕床も修正䟝頌をせざるを埗ないずいう課題がありたした。たた、開発サむドずしおも、修正を含め ”ちょっずしたコヌディング” が頻発するこずで業務が圧迫され、スムヌズな運甚ずは蚀い難い状況でした。  これらの課題を解決したいただ、CMS導入など倧掛かりなこずはできない・・・。 そこで、生成AIを䜿っお今回開発した運甚ツヌル「お知らせHTML䜜成ツヌル」を䜜るに至りたした。  - 解決したかった課題たずめ 入皿時に手元で完成圢を確認できるようにする修正を無くす HTMLコヌディング䜜業を無くす業務圧迫の改善 - 「Excel・WordのHTMLファむル出力」や「入皿時に生成AIでHTMLファむル化」ではダメだった理由 これらの方法でもHTMLファむルを䜜成できたすが、ここで必芁ずされるHTMLファむルは単玔な文章だけでなく、アプリ内のボタンやYouTubeの埋め蟌み動画、デザむンが斜されたトピック゚リアなど、耇雑な内容を含んでいたした。たた、Webの知識がない担圓者でも手元で完成圢を確認できるように、䞀発で高い品質のHTMLファむルを䜜成する必芁があったため、これらの方法では運甚が難しいず刀断したした。 手順必芁最小限の機胜を定矩する たずやったこずは、必芁最小限の機胜は䜕かを曞き出すこずです。あれやこれや理想を蚀い出すずキリがないものですが、 非゚ンゞニアの自分が䞀人で開発するのですから、ツヌル開発を倢で終わらせないためMVP開発を目指すこずにしたした。 - 私が曞き出した必芁最小限の機胜 フォヌム圢匏で入力した内容を、HTMLファむルずしお出力できる 必芁な入力欄は「タむトル・日付・芋出しetc 」 入力欄はナヌザヌの任意で、いく぀でも远加・削陀できる 出力するHTMLファむルには、指定のデザむンを反映する 出力するHTMLファむルには、指定の蚈枬パラメヌタヌを反映する 手順プロンプトを曞く 手順で曞き出した必芁最小限の機胜をもずに、以䞋のようなプロンプトを曞いお生成AIぞ指瀺を出したした。 私はCopilotを䜿いたしたが、ChatGPT・Geminiなど、䜕を䜿っおも問題ないず思いたす。 - 私が曞いたプロンプト ナヌザヌが入力した"お知らせ"の内容を、 HTMLファむルずしおダりンロヌドできるWEBペヌゞを䜜成しおください。 # 必芁な入力欄 タむトル 日付 芋出し サブ芋出し 段萜 画像 ボタン Youtube埋め蟌みタグ # 指瀺 フォヌム圢匏で入力した内容を、HTMLファむルずしお出力できる 必芁な入力欄は䞊蚘を参照 入力欄はナヌザヌの任意で、いく぀でも远加・削陀できる 出力するHTMLファむルには、指定のデザむンを反映する 出力するHTMLファむルには、指定の蚈枬パラメヌタヌを反映する # 芏定のデザむンCSS https://〜 # 蚈枬パラメヌタヌ 〜〜〜 手順調敎する 手順のプロンプトを生成AIぞ入力するず、初手ずしおは十分すぎるアりトプットが返っおくるはずです。それを元に、䜕だかちょっず違うなあず少しこうだったら、ずいう郚分を生成AIず䌚話しながら調敎したす。 䟋 先ほどのフォヌムを、以䞋の指瀺を元に改善しおください。 # 指瀺 画像の入力欄はURLではなく、その堎でアップロヌドできるようにしおください。 難しい手順は螏たず、誰でも画像を反映できる状態が望たしいです。 - 調敎する時のポむント 調敎時は、改善点を぀ず぀指瀺しお進めるこずをおすすめしたす。䟋えば、「画像は〜」「ボタンの動䜜は〜」「入力欄は〜」ず耇数の改善点を䞀気に䌝えおしたうず、生成が倱敗した際に、どこで躓いたか分かりにくくなるためです。たた、同じ理由で、生成されたコヌドやプロンプトの履歎は、バヌゞョンを付けお残しおおくこずをおすすめしたす。 䌚話をしながら、少しづ぀調敎しおいきたしょう - 粘り匷く調敎すれば、想像以䞊のものを生成できる 私の堎合、この調敎手順で必芁最小限の機胜を実珟したのはもちろんですが、最終的には以䞋のような機胜たで生成し、組み蟌むこずに成功したした。 入力内容をプレビュヌする 入力欄の順番を埌から入れ替えられるようにする 過去に出力したHTMLファむルを読み蟌んで線集する 画像サむズが極端に倧きい堎合に゚ラヌを出し、リサむズ方法を案内する - デザむンを調敎しお仕䞊げる 最埌に芋栄えがよくなるようCSSを調敎したした。CSSは調べたり、これもたた生成AIに聞いたりしお調敎が可胜ですが、面倒な方は以䞋のようなプロンプトで生成AIに指瀺しおもいいかもしれたせん。 䟋 䞀般的な入力フォヌムずしお䜿いやすいデザむンずは、どのようなデザむンですか それでは、それらのデザむンを先ほどのフォヌムに反映しおください。 - 完成䟋 こちらが、実際に私が生成AIを䜿っお開発した運甚ツヌルです https://www.youtube.com/watch?v=F-eyKyS8HSo さいごに この運甚ツヌル開発によっお、圓初あったステップをステップに削枛するこずに成功したした。 ビゞネスサむドから、衚瀺したい内容をExcel・Word圢匏で入皿 開発サむドの誰かがコヌディングしおHTMLファむルを䜜成 ビゞネスサむドでHTMLファむルをチェック 開発サむドの誰かが修正 堎合によっおをくり返し Web反映 ↓ ビゞネスサむドから、衚瀺したい内容をHTMLファむルで入皿 Web反映 たた、ビゞネスサむドからは入皿が楜になったず嬉しい声をもらうこずができ、ビゞネスサむドも開発サむドも嬉しい、WIN-WINな業務改善ずなりたした 私個人ずしおは、同じプロゞェクトのバック゚ンド゚ンゞニアに「昔ならこれず同じものを人の手で開発するのに、週間ほどの時間ずお金がもらえたのに 」ず嘆かれたこずが印象的でした。゚ンゞニアではない私が、それだけの䟡倀あるものを䞀人で開発できたこずに驚いおいたす。 生成AIには怖さすら感じるこずがありたすが、怖がっおいおも䜕もならないので、たくさん掻甚したいそう匷く思い盎すきっかけになりたした。 䜕事も同じですが、コツは䜕床もチャレンゞするこずだず思いたす笑最埌たでご芧いただき、ありがずうございたした。
This article is part of day 12 of KINTO Technologies Advent Calendar 2024 🎅🎄 Introduction Hello. Yamayuki here, from the Producer Team in KINTO Technologies’ Mobile Development Group.  Our company offers many opportunities to explore the use generative AI.  While I am not an engineer, I took the initiative to experiment with generative AI to develop an operational tool for business-side staff. This tool simplifies the creation of HTML announcements. When I share this with others, they often get the idea that I pulled off the amazing feat of building generative AI into it, but that is not the case. What I actually did was use generative AI to help develop it. This article is about that process. Even non-engineers can develop solutions to improve work! Having experienced this firsthand, I wanted to share it as a concrete example. Background: Why I Decided to Do It For the apps I am responsible for, tapping a push notification opens the announcement details page. This page is a web view that displays announcements by uploading an HTML file to the web. The operation looks as follows: (1) Business-side staff submit an Excel or Word manuscript of the content they want to display. (2) Someone on the development side codes the HTML file. (3) The business-side staff review the HTML file. (4) Someone on the development side makes corrections (Steps 3 and 4 were repeat in some cases.) (5) The content is reflected on the web.  Manuscripts were submitted at a pace of approximately two per week. A challenge with this process was that the business-side staff submitting the manuscripts did not have the final version ready for review, leading to repeated requests for corrections. Additionally, the frequent need for "a bit of coding" to handle submissions and corrections became a significant burden for the development team as well. Overall, the process was far from efficient or smooth.  We wanted to solve these issues! But at the same time, we were unable to do any drastic changes like introducing a CMS. That is when I decided to leverage generative AI to develop the operational tool I want to discuss in this article: a tool designed to create HTML for announcements.  - Issues we wanted to solve (Summary) Allow manuscript submitters to have the finalized version available for review during submission (eliminating the need for corrections) and remove the necessity for HTML coding (reducing the workload). - Why neither using Excel/Word’s HTML file output nor relying on generative AI to create HTML files during manuscript submission provided an effective solution While these methods can generate HTML files, the requirements in this case went beyond simple text. The HTML needed to include complex elements such as in-app buttons, embedded YouTube videos, and intricately designed sections for specific topics. Additionally, it was essential to produce a high-quality HTML file in a single step, allowing staff with no web expertise to have the finalized version readily available for review. As a result, I concluded that relying on these methods would complicate the operation. Step 1: Define the Minimum Features Required The first thing I did was write down what the minimum features it would require were. I could have written a virtually never-ending want-list, but since I am a non-engineer, I decided to aim for an MVP to ensure that developing the tool would not end up as just a dream. - The minimum required features I wrote down (1) You can input content via a form and get it output as an HTML file. (2) The required input fields are the title, date, headings, etc. (3) Users can add or delete input fields at will. (4) The outputted HTML file reflects the specified design. (5) The outputted HTML file reflects the specified measurement parameters. Step 2: Write a Prompt Based on the minimum required functions I wrote down in step 1, I wrote the following prompt to give instructions to the generative AI. I used Copilot, but I doubt it really matters which one I had used (notable alternatives being ChatGPT and Gemini). - The prompt I wrote Please take announcement content inputted by the user, and create a web page of it that can be downloaded as an HTML file. # Required input fields Title Date Headings Subheadings Paragraphs Images Buttons YouTube embedding tags # Instructions (1) Content inputted via a form can be outputted as an HTML file. (2) See above for the required input fields. (3) Users can add or delete any number of input fields at will. (4) The specified design is reflected in the outputted HTML file. (5) The specified measurement parameters are reflected in the outputted HTML file. # Prescribed design (CSS) https://... # Measurement parameters ... Step 3: Make Adjustments By inputting the prompt from step 2 into the generative AI should produce output that is more than adequate for a first attempt. Based on that, you then adjust the parts that are a slightly off or could use a little tweaking, engaging with the generative AI as you go. Example Please improve the previous form based on the instructions below. # Instructions Make it so that instead of having to enter a URL into the image input fields, you can upload the images right there and then. Preferably, anyone should be able to include images without having to go through a difficult procedure. - The key point when making adjustments When making adjustments, I suggest providing instructions to focus on one improvement at a time. For instance, if you give instructions like, "Images should..., button behavior should..., and input fields should...," you’re asking generative AI to handle multiple improvements at once. If it makes a mistake, it can be challenging to pinpoint where things went wrong. For the same reason, I also recommend maintaining a version-numbered history of the generated code and prompts. Make adjustments gradually, engaging in a step-by-step conversation as you go! things little by little, talking to it as you go! - Through persistent fine-tuning, you can achieve results that surpass your original expectations! In my case, this adjustment process not only allowed me to achieve the minimum required features but also successfully generate and incorporate the following additional ones. Preview the inputted content. Let the order of the input fields be changed later. Import and edit previously outputted HTML files. If an image is excessively large, display an error message and provide instructions on how to resize it - Adjust and polish up the design. Finally, I adjusted the CSS to make it all look nicer. You can adjust the CSS by reviewing the results and providing feedback to the generative AI as you go. However, if that feels cumbersome, you might prefer to provide instructions to the generative AI using a prompt like this: Example What kinds of designs make for easy-to-use general input forms? Okay, please reflect those designs in the previous form. - Finished example Here is the operation tool that I actually developed using generative AI! https://www.youtube.com/watch?v=F-eyKyS8HSo Conclusion Developing this operational tool successfully reduced the initial five-step process to just two. Before: (1) Business-side staff submit an Excel or Word manuscript of the content they want to display. (2) Someone on the development side does codes the HTML file. (3) The business-side staff check the HTML file. (4) Someone on the development side makes corrections (Steps 3 and 4 were often repeated.) (5) The content is reflected on the web. ↓ After: (1) Business-side staff submit an HTML manuscript of the content they want to display. (2) The content is reflected on the web. Additionally, I received positive feedback from the business side, noting that submitting manuscripts had become much easier. Both the business and development teams were pleased, making it a win-win improvement for everyone involved! What particularly stood out to me was when the back-end engineers on the same project pointed out that, in the past, it would have taken about two weeks and a significant amount of money to develop the same functionality manuall. I’m amazed that, as a non-engineer, I was able to independently develop something so valuable. Generative AI can feel intimidating, but letting fear hold me back won’t help, so I’m determined to make the most of it! This experience truly made me reconsider my approach to generative AI in a serious and thoughtful way. This applies to everything, but I believe the key is to keep trying lol. Thank you for reading all the way to the end!
Introduction Hello. I’m Hiroya (@___TRAsh) from the Mobile App Development Group. At our company, we have several in-house product teams, and many of them use Xcode Cloud. Xcode Cloud is an official CI/CD service provided by Apple that automates iOS app builds and CD (deployment to TestFlight). In this post, I’ll cover how to integrate a private repository as a library in Xcode Cloud. Since there weren’t many references available, I faced some challenges getting it to work and wanted to share the solutions I found. Target Readers This guide is intended for readers with some experience in iOS development, especially in setting up CI/CD for iOS environments. Environment - Using Xcode 15.4 - Managing libraries with SwiftPM - Referencing a private repository in libraries - Deploying to TestFlight with GitHub Actions + Fastlane Objective To shift TestFlight deployments from GitHub Actions + Fastlane to Xcode Cloud. By doing so, we can reduce dependency on Fastlane and minimize the tools required in the app submission process. Additionally, Xcode Cloud allows direct reference to Apple Developer certificates, making it easier to manage the certificates needed for app submission. Challenges Xcode Cloud offers many benefits, but using a private repository as a library requires user authentication. Since Xcode Cloud does not natively support these authentication settings, an extra step is needed to reference a private repository as a library. To achieve this, we can use the ci_scripts/ci_post_clone.sh provided by Xcode Cloud to set up the necessary authentication, allowing access to the private repository. .netrc configuration Since Xcode 12.5, .netrc has been supported as a way to store usernames and passwords,`` which can be automatically accessed during git clone operations. By placing in ~/.netrc , the authentication information is automatically applied. Additionally, for managing the private repository as a GitHub Release, I’ve added api.github.com to the .netric configuration. touch ~/.netrc echo "machine github.com login $GITHUB_USER password $GITHUB_ACCESS_TOKEN" >> ~/.netrc echo "machine api.github.com login $GITHUB_USER password $GITHUB_ACCESS_TOKEN" >> ~/.netrc I set my username and access token as secrets in Xcode Cloud’s environment variables and configured ci_post_clone.sh to reference them. Adding the Repository URL In Xcode Cloud settings within App Store Connect, I added the repository URL of the library under Additional Repositories . Removing Settings with Defaults Delete Even after configuring access to the private repository’s library, I encountered an issue where the library dependencies couldn’t be resolved, resulting in the following error: :::message alert Could not resolve package dependencies: a resolved file is required when automatic dependency resolution is disabled and should be placed at XX/XX/Package.resolved. Running resolver because the following dependencies were added: 'XXXX' ( https://github.com/~~/~~.git ) fatalError ::: This error occurs because, on Xcode Cloud, SwiftPM does not reference Package.resolved and instead attempts to resolve package versions automatically. To fix this issue and allow the build to succeed, I deleted certain Xcode defaults. defaults delete com.apple.dt.Xcode IDEPackageOnlyUseVersionsFromResolvedFile defaults delete com.apple.dt.Xcode IDEDisableAutomaticPackageResolution Although I applied these two settings, I couldn't clearly identify the difference between them... To get more information, I ran the xcodebuild help command locally, where I found some similar settings that could help clarify their roles. $ xcodebuild -help ... -disableAutomaticPackageResolution prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file -onlyUsePackageVersionsFromResolvedFile prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file This prevents SwiftPM from automatically resolving packages to versions other than those recorded in the Package.resolved file. However, both settings seem to do exactly the same thing, as no additional differences were found. I also came across a similar question in a SwiftPM issue thread, and this approach was confirmed to work. So, for now, I believe this setup is sufficient. https://github.com/swiftlang/swift-package-manager/issues/6914 For now, by deleting these two settings, SwiftPM will only refer to Package.resolved for library dependencies and resolve them. Conclusion By configuring .netrc in ci_scripts/ci_post_clone.sh , which Xcode Cloud references before starting, I was able to access the private repository. Additionally, setting the defaults delete ensures that SwiftPM resolves dependencies based solely on Package.resolved, allowing the build to succeed on Xcode Cloud. #!/bin/sh defaults delete com.apple.dt.Xcode IDEPackageOnlyUseVersionsFromResolvedFile defaults delete com.apple.dt.Xcode IDEDisableAutomaticPackageResolution touch ~/.netrc echo "machine github.com login $GITHUB_USER password $GITHUB_ACCESS_TOKEN" >> ~/.netrc echo "machine api.github.com login $GITHUB_USER password $GITHUB_ACCESS_TOKEN" >> ~/.netrc Lastly Fastlane is a great tool that has been around for a long time, but by using Xcode Cloud, the process of submitting an app has been simplified. As mentioned earlier, Xcode Cloud offers numerous benefits, so I encourage you to consider implementing it in your workflow. Appendix https://developer.apple.com/documentation/xcode/writing-custom-build-scripts https://speakerdeck.com/ryunen344/swiftpm-with-kmmwoprivatenagithub-releasedeyun-yong-suru https://qiita.com/tichise/items/87ff3f7c02d33d8c7370 https://github.com/swiftlang/swift-package-manager/issues/6914
この蚘事は KINTOテクノロゞヌズアドベントカレンダヌ2024 の7日目の蚘事です🎅🎄 はじめに こんにちは CloudInfrastructure G (Osaka Tech Lab) の井朚です。 今回は、Osaka Tech LabのメンバヌでMVP開発手法を甚いお生成AIを掻甚した䌚話APIを䜜成したこずに぀いおい話したいず思いたす。 ちなみにMVP(Minimum Viable Product)開発ずは 最小限の機胜を備えたものをたず䜜成しお、ナヌザ ( 今回だず発案者のPdM ) のフィヌドバックを受けながら怜蚌改善を進めおいく手法です。 今回のざっくりずした芁件に察しお非垞に効率的な開発手法だず思っおいたす。 䜜成しようず思ったきっかけ 至極圓然な理由です。 KINTOテクノロゞヌズは、東京、名叀屋、倧阪 (Osaka Tech Lab) に拠点がありたすが、瀟員の89割近く東京に偏っおいたす。 たた、拠点ごずで担圓を分けおいるわけではないため、東京のプロゞェクトにそれぞれ参加しお仕事を行っおいたす。 この状況はよくある状況で、問題があるわけではないですが 倧阪メンバヌは他の拠点に比べお仲が非垞にいいです自己申告 そうなるず、倧阪メンバヌだけでやりたいよねずなるのは圓たり前ですね。 そのタむミングで、生成AIを掻甚した䌚話が成り立぀のかを考えおいるPdMずの出䌚いは必然です。 テヌマ 今回は、生成AIを掻甚した䌚話が成り立぀のかどうかの仮説をMVP開発で怜蚌するこずにしたした。 䌚話ずいっおもいろいろあり、接客察応も䞊叞の報告も䌚話になりたす。 緊匵したかたい䌚話ではなく、家族、友人ず話すような「自然な安心した䌚話ができるのか」を焊点に充おおいたす。 ずりあえずできたもの 党䜓構成 ※䌚話にロボットがいたほうがいいずの刀断からナカむ工孊様の垂販ロボット (BOCCO emo) を利甚 Azure構成図 ![Azure構成図簡易版](/assets/blog/authors/norio_iki/chmcha_azure_architecture.png =700x) 䜜成するにあたり考慮した点 時間 今回のゎヌルは、生成AIを掻甚した䌚話が成り立぀のかをテヌマずしたしたが 時間ず人をかけおできたずしおもそれは、もう生成AIを利甚した䌚話が䞖の䞭にあるのでそれはできるでしょうになりたす。 たた今回に぀いおは、䌚話ずいう人は簡単にやっおいるけど実は考えるず非垞に奥深いテヌマずなっおおり、 やりたいこずなどが山ほど出おくるテヌマずなっおいたす。そのため時間があればいくらでもできるものです。 MVPに時間をかけお䜜成しおもそれはMVPの䟡倀はないず考え、今回は決めた時間を超えない前提で進めたした。 䜿甚した時間 芁件怜蚎/MVP䜜成 2 Day フィヌドバックを受けながら怜蚌改善 15 hour(Max) 実際に䜕をやったか 芁件怜蚎/MVP䜜成 今回のMVP開発における環境は決めおいなかったのですが、生成AIを掻甚したシステムをどのように䜜ればいいかも知芋がほずんどない状態でした。 このたただず「自然な安心した䌚話ができるのか」のテヌマを怜蚌する前に生成AIのシステムをどのように䜜ればいいかからの怜蚌からスタヌトしなければいけないずころでしたが 生成AIのシステムの知芋はAzure Light-upのプログラムを提䟛しおいる ZENARCHITECTS様 の協力を受け、自分たちは今回のテヌマに集䞭できる状態を䜜り䞊げおいたす。 ZENARCHITECTS様には、生成AIシステム構築の䌎走だけではなく 今回ざっくりずしたテヌマから生成AIを利甚するために気を付けたほうがいい点なども実経隓からのアむデアをいただき、 日間でMVPを完成するこずろたで匕っ匵っおいただいおおりたす。 フィヌドバックを受けながら怜蚌改善 実際に利甚しおみたコメントを受け、開発するメンバヌで話し合い改善内容を決定。 珟圚の堎所から䌚話をする機胜を远加したり、カフェばかり話すロボットを盎すためプロンプトずか 気が付いたずころをフィヌドバックしおもらうサむクルを1カ月間 (15h) を利甚しお実斜したいた。 珟圚の堎所から䌚話する機胜の怜蚌には、堎所情報を倉えながらオフィスで怜蚌するのではなく、本圓に移動しながらの怜蚌も実斜。 車のサブスクを提䟛するKINTOならではの、車䞊でフィヌドバックを受けながらデプロむを行うなどのリアルタむムでのアップデヌトも 内補ならできる察応です(こんな感じで怜蚌改善を繰り返したした) ![ドラむブしながらのデプロむ](/assets/blog/authors/norio_iki/drive_deploy.jpg#right =400x) さいごに 今回䜜成した生成AIを掻甚した䌚話APIに぀いおは、珟圚瀟内で将来性に぀いお怜蚌䞭ずなりたす。 将来かかるず想定されるコストに察しお、䟡倀が䞊回ったらさらに開発を進める予定です。 だた、もし時期早々の刀断になっお継続開発が䞭止ずなる堎合もありたす。 しかし、たずえ継続がなくおも今回確認した結果ず、新しい分野生成AIシステムのAzure開発の経隓ずいう成果が残りたす。 その成果は、既存システムぞのフィヌドバックにいかせたり、新たなアむデア創出の糧になるものです。 今回は、たずえ継続がなくおも倱敗ではないず考えたす。倱敗しないMVP開発を行うにあたり、垞に前進しお むノベヌション・サむクルを回しお行ける環境を䜜っおいけるず考えたす。 今埌ずもMVPでのチャレンゞ自䜓は進めおいきたいず思いたす たた、もう少し詳しく内容を知りたい方は、ZENARCHITECTS様の事䟋玹介に蚘茉されおおりたすのでそちらをご芧くださいたせ Azure Light-up
はじめに こんにちはコヌポレヌトITG兌技術広報G 孊びの道の駅チヌム所属の明田です。 普段はコヌポレヌト゚ンゞニアずしお、IT機噚に関するオン/オフボヌディングやグルヌプ内のプロセス/業務改善をしたりしおいたす。 今回はコヌポレヌト゚ンゞニアの自分がひょんなこずから「孊びの道の駅プロゞェクト」に参加し、掻発的に開催されおいる瀟内勉匷䌚の動画が自発的に集たるポヌタルを぀くった話を玹介しようず思いたす。 きっかけ瀟内勉匷䌚を埌で芋る手段っおなくないか 圓瀟、䞋蚘のように䜕床か蚘事で玹介しおいる通り瀟内勉匷䌚が頻繁に行われおいたす。 https://blog.kinto-technologies.com/posts/2024-04-23_%E5%AD%A6%E3%81%B3%E3%81%AE%E9%81%93%E3%81%AE%E9%A7%85%E3%81%AF%E3%81%98%E3%82%81%E3%81%BE%E3%81%97%E3%81%9F/ https://blog.kinto-technologies.com/posts/2024-05-21-%E5%AD%A6%E3%81%B3%E3%81%AE%E9%81%93%E3%81%AE%E9%A7%85-iOS%E3%83%81%E3%83%BC%E3%83%A0%E3%81%AE%E5%8B%89%E5%BC%B7%E4%BC%9A%E3%81%AB%E7%AA%81%E6%92%83/ 開催埌は瀟内Slackチャンネルにお録画ファむルが投皿されたすが、コヌポレヌト゚ンゞニアずしお次のこずが瀟内の課題だず感じおいたした。 瀟内にはConfluenceやSharepoint、Boxずいったようにドキュメントファむルを共有する堎所はあるが、動画コンテンツの眮き堎が定たっおいない。 特に、瀟内の有益な勉匷䌚の動画ファむルは瀟内Slackチャンネルにお録画ファむルが投皿されるだけで埌から探すこずが難しい 党瀟情報共有チャンネルがメむンの投皿堎所になっおおり、他の業務関連情報等も流れおくるこずから簡単なスクロヌルだけでは探せない状態だった 勉匷䌚開催埌に入瀟したメンバヌはそもそも勉匷䌚があったこずも、その動画やファむルがあるこずを知る術がない これらを解決するにはどうしたらいいだろうか ず考え、思い぀いたのが動画プラットフォヌムを構築しお勉匷䌚の動画が䞀堂に集たる堎所を䜜ったらどうか、ずいう案でした。 はじたり孊びの道の駅プロゞェクトのメンバヌに突撃しおみた 勉匷䌚の動画が集たる堎所を䜜る、ず思ったずきにすぐに思い぀いたのは孊びの道の駅プロゞェクトの存圚でした。孊びの道の駅は「瀟内の「勉匷䌚」ず「勉匷䌚」が亀わる「道の駅」ずしお、勉匷䌚を軞にした瀟内掻性を支揎」する掻動を行っおおり、自分がやりたいこずず掻動がバッティングしないか・もし怜蚎しおいるなら協力しお進められるずいいな、ず勝手な思いを抱きたした。 鉄は熱いうちに打お、ずいうこずで同じグルヌプか぀孊びの道の駅に参加しおいる きんちゃん にたずはその旚を盞談したお返事が「いいですね賛成です」ずのこず。めちゃくちゃ嬉しかった蚘憶がありたすその埌、コヌポレヌトITGずしお本件を進めおOKずなった埌に早速他のプロゞェクトメンバヌにも盞談しに行きたした。 圓時のSlackのやりずりが、孊びの道の駅メンバヌの熱さが䌝わるず思うため皆さんに玹介したす。 たずは私の盞談文章がこちら。 ここで絵文字リアクションがすごいこずにお気づきでしょうかこの勢いのたたのお返事がこちらです。 話がずっおも早いその埌、集たった圓日に「明田さんも孊びの道の駅に入っちゃえばいいじゃん」ずお誘いをいただき孊びの道の駅メンバヌずしお動画プラットフォヌムを進めおいくこずになりたした。 本線動画プラットフォヌムを構築・瀟内展開した このブログを読んでくださっおいる方も、党瀟MTGやグルヌプ内の勉匷䌚を集めた動画プラットフォヌムを瀟内に展開したいずいう方はいらっしゃるず思いたすのでここからは実斜内容の䞭身ず、その䞭からいく぀かピックアップしおなぜそれを実斜したのかを玹介したす。 実斜内容 動画プラットフォヌムの構築 瀟内で䜿甚されおいたSharepointサむトを掻甚し、動画掲茉方法等をリニュヌアル 動画収集方法の決定 瀟内の公開OKな䌚議、瀟内勉匷䌚の動画を収集する 䌚の䞻催者に録画ファむルず䜿甚した公開可胜なファむルをSharepintサむトのドキュメントにアップロヌド Sharepointサむトのトップペヌゞは「匷調衚瀺されたコンテンツ」機胜を䜿甚し、勉匷䌚名でフィルタヌ蚭定する。 䞀床の蚭定以降、同じ勉匷䌚名の動画がアップロヌドされるずトップペヌゞに自動で動画が掲茉される仕組み 呚知、運甚 8月末の党瀟MTGで動画プラットフォヌム玹介動画収集の䟝頌 Q.なぜSharepointを採甚したのか A. 圓瀟がグルヌプりェアでMicrosoft365を採甚しおいるため 動画プラットフォヌムはYoutubeやVimeo、Brightcove等ありたすがこれらは新芏に契玄をしなければならなかったこずや元々動画収集は䌚の䞻催者に実斜しおほしいずいう芁件があったこずから慣れ芪しんでいるSharepointを採甚したした。 Q.なぜ䌚の䞻催者自身に動画をアップロヌドしおもらう方針にしたのか A. 䞍芁な個所の削陀などの線集など、䞻催者自身に行っおほしかった 動画プラットフォヌムぞアップロヌドすべきかどうかは自䞻的に刀断しおもらうのがベストだず考えた ここを第䞉者である私達孊びの道の駅メンバヌが䜜業を代行しお請け負うこずもできたしたが、勉匷䌚開催前のわちゃわちゃした䌚話やリアルタむムだからこそ䌝えられるこず等を動画にも含むかどうかずいうこずず、動画プラットフォヌムにアップロヌドすべきかを刀断できるのは、勉匷䌚の䞻催者がいちばん適任だず考えたした。 Q.なぜ「匷調衚瀺されたコンテンツ」機胜を䜿うこずにしたのか A. シンプルに楜だったから アップロヌドしおもらった動画を芋おもらうためにはどうしたらよいか、を考えたずきに文字よりも画像衚瀺・䜕回かクリックした先で動画を芋るよりもトップペヌゞから興味ある内容を芋るほうがリヌチ数が高くなるず仮定したした。 その埌、動画ぞのアクセス方法をいく぀かのパタヌン分䜜成しお孊びの道の駅メンバヌに芋おもらったずきにサムネむル衚瀺ができ、か぀勉匷䌚ごずに動画がたずたっおいる状態がSharepointサむトのトップペヌゞに䞊んでいる状態がいちばん閲芧者の興味を匕くずいう答えになったこずでそれが実珟できる「匷調されたコンテンツ」機胜を掻甚するこずになりたした。 䜿甚䟋 参考 https://support.microsoft.com/ja-jp/office/匷調衚瀺されたコンテンツの-web-パヌツを䜿甚する-e34199b0-ff1a-47fb-8f4d-dbcaed329efd これを䜿う䞊での泚意ですが、アップロヌド盎埌の動画は勉匷䌚名動画ファむル名で行うフィルタヌに匕っかからないこずがありたす。そのずきは少し時間を眮いおから、再床詊しおみおください。 Q.呚知は党瀟MTGの1回だけなのか A. 新入瀟員向けのオリ゚ンテヌションで毎月玹介 毎月ある党瀟MTGの孊びの道の駅技術広報Gの枠で玹介 冒頭に蚘茉した通り「勉匷䌚開催埌に入瀟したメンバヌはそもそも勉匷䌚があったこずも、その動画やファむルがあるこずを知る術がない」ずいう課題を持っおいたこずから、ここはもちろん察凊したした。この2枠あるこずで、存圚感を出すこずができおいたす おわりに瀟内の孊びをいろいろな方向からこれからも支揎したす 動画プラットフォヌムは党瀟MTG以降、アクセス数が著しく枛少するこずもなく勉匷䌚動画も収集されおおり元々感じおいた課題を解消する䞀助になり぀぀あるのではないかず感じ぀぀ここたで、「瀟内の課題感」「孊びの道の駅ずいう匷力なサポヌタヌが瀟内にいる話」ず「本線/実際の構築話」の3本をお話したした。 匊瀟のいいずころは、新しいチャレンゞに寛容であるずころでたさに今回の取り組みはそれを䜓珟できおいるず感じおおり、本線にたどり぀くたで長く語っおしたいたしたが、ここたでこの蚘事を読んでくださった皆さんにはそれが䌝わったのではないかず勝手に思っおおりたす。 珟圚、孊びの道の駅は1プロゞェクトから技術広報Gの1チヌムずしお掻動しおいたす。これからも倉わらず「瀟内の「勉匷䌚」ず「勉匷䌚」が亀わる「道の駅」ずしお、勉匷䌚を軞にした瀟内掻性を支揎」する掻動を行い、それらの掻動をテックブログ等で玹介しおいきたす。匕き続きよろしくお願いしたす