TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

969

My name is Yuya Sakamaki from KINTO Technologies. I am usually involved in anything from data analysis to proposing strategies, and developing features using machine learning. Previously, I was in charge of AI functional development at Prism Japan . I conducted an internal comparison test of Cursor and GitHub Copilot, and I would like to share the results with you. Premise At KINTO Technologies, there's a policy that allows GitHub Copilot to be used as a standard tool. I will test whether using Cursor Editor can further improve productivity. Cursor An AI-powered code editor, created as a fork of Visual Studio Code. Github Copilot It’s a tool co-developed by GitHub and OpenAI, available as a plug-in for various IDEs such as Visual Studio Code, JetBrains, Eclipse, and more. Disclaimer This article contains many of my own opinions. This is not meant to undermine the usefulness of Cursor. Tested Plans Copilot Enterprise ... $39 / month Cursor Business ... $40 / month Conclusion As of February 2025 , I feel that there is almost no difference in functionality between GitHub Copilot and Cursor. As of December 2024 , I would have liked to recommend Cursor, but with the incredible speed at which Copilot has progressed, I no longer feel the need to choose Cursor Editor again. Evaluation Period December 2024 to February 2025 Comparison Table (Copilot vs. Cursor) I have put together a table of some of the most common points that came up in the discussions using Copilot and Cursor together. The details of each item will be provided later. Item GitHub Copilot Cursor UI/Operability - Inline suggestions and chat ability in VSCode - Many operations by right-clicking - AI-powered shortcuts are displayed instantly - Inline menus are easy to understand QuickFix (completion of import, etc.) - VSCode standard quick fixes are powerful - Sometimes it is unstable and doesn't work - Fix in Composer etc., is a little troublesome Automatic reflection of similar corrections - Need to start writing something first - Tabs predict and suggest the next correction to make (useful for making corrections to multiple places) Model selection - Can be changed, but the variety is limited - Abundant additions and selections (multiple AI models can be used) Rule file - Can be done by changing the settings in .github/copilot-instructions.md - The .cursorrules file enables you to apply rules per project Loading documents (such as @Docs) - Supported by plug-ins, etc. - The @Docs feature enables you to load documents for frameworks and libraries Agent functions - Similar functions available in Copilot Chat / Copilot CLI - Automatic execution in agent mode, try & error Automatic test code generation - Generation is possible (although accuracy varies) - Can be generated in the same way, but the accuracy is questionable. Corrections are needed. Price (Business/Enterprise) $39 $40 *The content of the evaluation is subjective as of February 2025. Pros of Cursor (when compared to GitHub Copilot) AI-powered shortcuts are displayed immediately Since AI-powered shortcuts are displayed automatically when you select a code, the need to remember special operations can be eliminated. Is there something similar in Copilot? Inline chat is possible. However, there is no COMPOSE mode to reference other files, and you need to call it up from the right-click menu, so it requires a little bit of effort. Suggests similar corrections together When you want to apply similar corrections to multiple places, Cursor will predict "where and how to fix next" in order on the tab. Is there something similar in Copilot? It doesn't make predictions until you start writing something first, so it is difficult for it to provide bulk suggestions as smoothly as with Cursor. A similar function is now available in Copilot *There were some changes as I was writing this article. Flexibility with selecting and adding models Cursor is designed to make it easy to add and select any model you want. Is there something similar in Copilot? Copilot Chat also enables model switching, but the scope of what can be done is currently limited. The @Docs feature enables you to load documents for frameworks and libraries Having it read the documents in advance will improve the accuracy of its answers. Is there something similar in Copilot? It is possible to achieve something similar using plug-ins, but the standard features are not that extensive. Automatic execution of try & error in agent mode In agent mode within Cursor, the AI will automatically execute commands under a certain level of authority and perform trial and error. Is there something similar in Copilot? Similar features are starting to appear in Copilot CLI and Copilot Chat, but they still require some fine tuning. While still just a preview version, agent mode is now available in Copilot . Cons of Cursor (when compared to GitHub Copilot) Import completion is a bit troublesome VSCode + Copilot will automatically complete imports, but Cursor will not import unless you hover the mouse cursor over it and select a quick fix. Copilot offers greater stability and overall superiority Automatic test code generation is not very accurate Automatically generated tests often don’t pass on the first run and usually require some adjustments. The same is true for Copilot, but I didn't get the impression that Cursor was much of an improvement. There are still many parts that are not suitable for operation Depending on the app being developed, Cursor contained more AI functions than needed, which confused some team members. The Difference between Cursor's "Composer" and "Chat" Cursor is divided into two main modes: Composer and Chat . Since there are differences in usage and the handling of context, I have put together a quick summary of the results of the internal comparison. Functional Items Composer Chat Context understanding Automatically links to the current file. Auto-suggests related files. Initially the context is empty. Must be shared manually as needed Main uses Mainly code generation and editing. Easy to correct suggested content immediately using inline Mainly used for receiving general questions and explanations. Suitable for longer exchanges History management Auto-saves in the control panel Chat history is selectable UI Also usable in inline menu. Can also be operated from the side menu Side menu only Code block execution Not possible Possible (execute commands via Chat in the side menu) When to use Composer Code corrections with simple context When you want to generate and edit code quickly When to use Chat Code corrections with larger context Analyzing error messages, general programming questions When you want to work while maintaining context for a long period of time Conclusion Again As of February 2025, there are no huge functional differences between Copilot and Cursor In fact, Copilot's progress has been faster than expected, and it seems that within a few months, Copilot filled the gap that I thought Cursor was going to win as of December 2024. There are certainly pros to using Cursor As an AI-powered IDE, Cursor still has its strengths, such as dedicated shortcuts and agent mode. However, since Copilot has also been making progress in supporting these features, the pros of newly introducing it may be somewhat diminishing. Which one will you use in the end? If GitHub Copilot is already available as standard equipment within the company, you will likely be able to get by with just Copilot for the time being. That said, if you're wanting to embrace large-scale code corrections and chat-driven development, it's worth giving Cursor's Composer / Chat features a try, even though the Github Copilot AI agent is currently available to VSCode Insiders. Since they are almost the same in terms of price, it is good idea to choose based on the UI and ease of use. Development App Prism Japan - iOS Prism Japan - Android References Cursor - The AI Code Editor Getting code suggestions in your IDE with GitHub Copilot The above are the results of my extensive use of Cursor and GitHub Copilot. The speed of product updates is accelerating, so I would like to continue to test them regularly.
アバター
Hello! My name is Kita and I work on a travel media platform called Prism Japan (for Web, iOS, and Android) at KINTO Technologies. Since November 2024, I've been working on social media management for Prism Japan. Going beyond traditional advertising, the goal is to actively use social media as a way to grow brand awareness and create a channel to attract new customers. So here's a quick look back at how things went over the past four months! What We Achieved Let’s begin by looking at what we’ve accomplished while managing our social media accounts. Creating short-form videos Short-form content, especially on TikTok and Instagram, is a key element in today's social networking environment. The algorithms are designed to show short videos to users beyond your followers, and depending on how viewers react, your reach can grow quickly. Increasing awareness using social media was a required subject, so we started by imitating popular videos that were going viral in similar categories. Based on those ideas, I picked up editing skills by experimenting with different editing tools. Here are the main tools I used: Tool Name Features Canva Has a free plan available at no cost Images and videos can be freely edited and easily shared with co-editors, making management simple. There was only one time the server crashed during editing, and I had to start over. Adobe Express Has a free plan available at no cost It's not much different from Canva, but I personally prefer this one because the server is stable that I never experienced any crashes while working. Capcut Has a free plan available at no cost Its main feature is video editing. It offers tons of social media-friendly features, such as text-to-speech, 3D image conversion, and the ability to use fonts that are commonly seen on social media. Note: The watermark in the free version doesn’t go over well on Instagram. Depending on your goals and preferences, you might favor one tool over another, but generally, all of them can get the job done—even with their free versions. Gaining impressions Across all platforms, we were able to gain a lot of impressions. Let's take a look at the period from November 1, 2024, when the full-scale operations started, to March 17, 2025 (the time of writing). X Impressions increased 293% to 62,500. Tiktok Since we only started using TikTok during this period, there’s no earlier data to compare, but our videos have been viewed 440,000 times so far. Compared to other platforms, it appeared to steadily accumulate views over time. Instagram Reach increased by 90%, reaching 50,000. *Note: Accurate comparisons for views and interactions couldn’t be made due to the absence of data prior to August 2024. This is especially noticeable on TikTok and Instagram, with peaks occurring around the New Year period. We think this growth was driven by a combination of people being on holiday and a well-timed post introducing shrines that were perfect for the New Year's visits. When comparing the period before and after full-scale social media operations, factors like posting more frequently and simply being active on the platforms definitely played a role. However, what really helped boost views was tapping into trends like video format, seasonality, and timely topics. I'd say these efforts helped "raise awareness" to some extent. Communication with users On X in particular, we were able to discover and connect with new audiences. Previously, we hadn't taken any active steps like following users or replying to posts. But since X prioritizes engagement in its algorithm, we began directly interacting with users interested in tourism. As a result, one user not only installed our app, but also posted a review after actually visiting one of the spots we featured! Even better, they gave us some great content ideas. If you have a product or service, I definitely recommend reaching out actively. It can lead to both new users and valuable feedback. What We Couldn't Achieve Next, let's take a look at what didn't go so well. Did not result in app installations or website traffic As mentioned earlier, we saw a noticeable increase in views and impressions. However, those numbers didn't translate into app installations or visits to the website. Below, I'll break down the results by platform. The evaluation period is set from November 1, 2024, when the full-scale operations started, to March 17, 2025. The following data reflects the number of app installs measured by AppsFlyer during the period. Note: We've decided not to disclose the specific number of installations. Tiktok & Number of app installs First, we investigated whether there was any correlation between TikTok metrics and app install numbers. Number of views This chart was shown earlier, but here we compared the number of TikTok views with the app installs. As you can see from a quick comparison of the graphs, there doesn't appear to be a clear correlation. For example, even though views peaked on January 2, 2025, there was no noticeable increase in installs. On the other hand, January 29, 2025 saw a spike in installs when there was no change in views. This also leads us to conclude that there is no correlation between TikTok views and app installs. Profile views Next is the number of profile views. We assumed that users who visited our profile showed some level of interest. Thankfully, profile views have been trending upward, but for now, it doesn't seem like they're having much of an impact on app installs. Likes What about the number of likes? At a glance, the trend in likes seemed to follow a similar pattern to views. Similarly, on January 2, 2025, when we got the highest number of likes, app installs didn't change. And on January 29, 2025, when installs spiked, likes didn't seem to contribute to that increase. Comments The total number of comments was 81, which is relatively low considering the number of views. There were even days with zero comments. The number of comments on February 17, 2025 was slightly higher at 4, but there was no change in installs. Shares The trend in shares was also similar to that of views and likes. We noticed firsthand how TikTok's algorithm tends to promote videos that get a lot of likes and shares, placing them on the For You feed, which in turn helps boost views. Instagram & Number of app installs Next up is Instagram. Views Similarly, large peaks around the New Year holiday. As mentioned earlier, that timing didn't lead to any noticeable change in app installs. On the other hand, there was some correlation over multiple days. Overall, the correlation wasn't strong, but compared to TikTok, there might be slightly more of a relationship between views and installs. Also, the reach (i.e. number of unique users) showed a similar trend as above, so I'll skip the details here. Interactions Lastly, interaction. On Instagram, this includes likes, saves, comments, and shares. Again, no correlation was found with app installs. Summary Overall, there was no correlation between social media activity and app installs. So for now, it's hard to say that our social media efforts drove user acquisition. However, this analysis is based on correlation only, so it's possible that some viewers went on to install the app later. But to be honest, it's challenging to measure that accurately. Traffic to the website We expected that social media would also contribute to attracting customers as a route other than SEO. In reality, we were able to drive some visits through Instagram stories and posts on X, but the total number was only around 300, which is quite small. We believe this is mainly due to the low follower count on X which limited our reach, and the lack of Instagram story viewers. Impressions increased, but follower growth was limited X: 425 followers On X, we were still lacking in follows and actions such as likes. Part of the reason is that we took a slower, more cautious approach while juggling other platforms over a long period of time. I think it's best to be more proactive with follows and likes early on to gain traction faster. But be aware that excessive following can lead to shadowbans or account suspensions. Tiktok: 155 followers Despite the high number of views, we barely gained any followers. I think there are two main reasons for this: One is that the content was too generic. I think it lacked originality and creativity. There wasn't anything that made users feel they had to follow the account or risk missing out. Second, the target was unclear due to the nature of Prism Japan targeting a wide range of people who are interested in travel. But on TikTok, we didn't narrow down the demographic (age, gender), so information ended up reaching a wide range of people. Instagram: 6,667 followers At first glance, this number may seem high, but since we stopped running ads, follower growth has been on the decline. It's often said that followers gained through ads tend to be less engaged. On top of that, our post style shifted significantly after we paused the ads, which may have further accelerated the drop-off in engagement. Key Characteristics & Improvement Points by Platform Here, I'll break down the strengths of each platform and post performances we observed during actual operations. TikTok: Content can go viral even without a large number of followers Top-performing post: 3 hidden spots in Tokyo perfect for a girls' trip! We intentionally set a very specific target audience for this post: "Girls traveling around Tokyo." This content guides users through the actual process of using the app to discover hidden gems. The spots featured are hidden gems with themes like "overseas-inpired," "anime-related," and "en-musubi (shrines dedicated to love and relationships)." As a result, 94% of traffic came from search, with queries like "Tokyo places to hang out for two girls" or "Tokyo sightseeing." We believe narrowing the target and showcasing true hidden spots played a key role in its success. Lowest-performing post: The Hibiya illuminations were so beautiful! The video that didn't perform well was the one about the Hibiya illuminations. We actually visited the location, captured the lights and ambiance, and tried to create an engaging video by syncing with the background music. Despite these efforts, the video barely got any views. The likely reason is we entered a highly competitive area filled with high-quality content under popular keywords like "Christmas" and "illuminations," making it hard to stand out. Summary Comparing the two, we found that the successful post had a clear target audience and offered useful content to that audience. As a result, the number of views increased significantly, along with key metrics like average watch time and completion rate. X: Communication with users is its strength Top-performing post: 4 recommended shrines and temples for autumn foliage This post highlighted a selection of spots where you can enjoy the beautiful combination of autumn leaves and traditional shrines or temples. It includes location details for each spot, highlights, and accompanying images. Lowest-performing post: Today is Christmas! This post featured a video created using generative AI, accompanied by the trending hashtag #MerryChristmas. The goal was to bring a more "human" feel to the account and to gain some impressions with the hashtag but the post didn't generate much engagement. Summary Looking across other popular posts on X, content that ties into seasonal themes or current trends tends to perform better. I also found that useful content, whether it's well-organized or clearly valuable to the viewer, plays a key role in performance. Instagram: Short videos make it easier to reach non-followers Top-performing post: [2024-2025 Season] 4 recommended ski resorts near Tokyo With the start of the winter ski season, we introduced ski resorts around the Kanto area using an image post. We listed the opening periods, hours, and pricing for each resort. As a result, the post received 10 saves, which was noticeably higher than other posts, and it reached 55.4% non-followers. In my opinion, Reels tend to reach non-followers even without many saves, while image posts usually stay within your followers, around 90 percent of the time. Looking back, I realized that creating posts people want to "save" is the most important. Lowest-performing post: AI diagnoses you! In contrast, this video introduced our app's "Search by mood" feature using a Reel. The intention was to have users recall how they would use the app by recording the actual app screen and walked through the steps, but it ended up being the least played video. I think the target audience wasn't clearly defined, and the value to the user wasn't communicated well. Summary While Reels and image posts differ in how easily they reach non-followers, I got the impression that a post's performance often depends on whether it gets saved. More than just offering useful content, it's important to actively encourage saves and review it regularly. Overall Summary & Future Direction Overall, we found that social media didn't make a major contribution to attracting customers. That said, we can't say no effect either. So, we'll continue to reflect and refine our approach moving forward. This article highlights two particularly important points: Clarify your target Information that is useful to viewers These may sound obvious, but I realized that these two points are essential for videos to driving performance regardless of the platform. We also used the same content from TikTok on Instagram, but because the user demographics differ between platforms, it didn't get much of a response. While it's ideal to tailor posts for each platform, our limited resources made it difficult to do so effectively. Each platform has its own unique characteristics and user demographics, so it’s important to identify what works best based on the specific purpose. References The 7 Golden Rules of Social Media Marketing
アバター
はじめに はじめまして。KINTOテクノロジーズでAndroidアプリ開発を担当しているJongSeokです。 ある日、資料を整理していて、Google Sheets に内容を移しながら整えていると、ふとこう思いました。 「これ、めっちゃ面倒くさくない……?」 同じような内容を何度も入力して、セルのフォーマットも整えて…… 難しくはないけれど、これって本当に人間がやるべきことなんだろうか?と感じました。 そこで、最近よく目にするAIツールで自動化できないか調べてみたところ、 出会ったのが MCP(Model Context Protocol) でした。 いくつかのツールを試してみた結果、 なんと自然言語の「一行だけ」で Google Sheets を操作できました。 この体験は、正直かなり衝撃的でした。 MCPとは? MCPは「Model Context Protocol」の略です。 名前は少し堅く聞こえるかもしれませんが、実際はとても直感的な役割を持っています。 簡単に言うと、 AIが実際のツールやサービスを自由に操作できるようにする「通訳」のような存在です。 この記事ではその中でも、Googleサービスと連携する事例にフォーカスして紹介していきます。 もっとわかりやすく言うと 普段使っているGmailやGoogle Drive、Google Sheetsなどのサービスは、人がクリックして操作します。 MCPは、そういったサービスを AIが自動で操作できるようにするものです。 たとえば、こんなことができます。 Google Sheetsに何かを記録したい時、通常はこんな感じかと思います。 シートを開いて セルをクリックして データを入力する MCPは、そういったサービスをAIが自動で操作できるようにするものです。 それでは、実際のデモを見てみましょう。 https://www.kinto-technologies.com/products/ 次のWebページを参考にして、新しいGoogleスプレッドシートを作成し、紹介されている製品・サービスを整理してください。 上記のように指示した場合の実際の操作イメージが、以下のデモ動画です。 ![demo](/assets/blog/authors/jongseok/mcp/demo.gif =1080x) このように一文で指示するだけで、AIがGoogle Sheets APIを呼び出し、シートに自動でデータを入力してくれます。 本当に簡単にデータ整理ができてしまいました。 MCP最近流行ってるの? 技術的に可能ということと、実際に人々が関心を持っているというのは別の話。 そこで、Google Trendsで「MCP」というキーワードの検索トレンドをチェックしてみました。 2025年3月から5月までのデータを見ると、 特に4月中旬から検索数が急激に増加しているのが分かります。 これは単なる興味を超えて、 開発者だけでなく一般ユーザーまでがMCPを実際に「使い始めている」ことを示しているとも言えます。 自分もその波に乗った、という感じです。 では、実際にMCPを試してみたい場合、どのような方法があるのでしょうか? MCPを活用するには、自然言語の命令を受け取り、それを実行できる環境が必要です。 実際、Claude(Anthropic)など、MCPと連携できるAIエージェントはいくつか存在しますが、今回私が選んだのは、AI機能が組み込まれたコードエディタ「Cursor」でした。 Cursorとは? Cursorは一言で言えば、AI機能が組み込まれたコードエディタです。 自然言語でコードを依頼すれば、AIが自動で提案・修正・説明などをしてくれます。 MCP の命令を書く際にも、Cursorを使えばよりスムーズに記述できます。 Cursorについてもっと詳しく知りたい方は、公式サイトやブログを参考にするのがおすすめです。 https://www.cursor.sh それでは、一緒に始めてみましょう MCPとGoogle Sheetsを連携して実際に自動化を体験するには、まずいくつかの準備が必要です。 難しい知識は不要なので、安心してください。一つずつゆっくり進めていきましょう。 事前準備 自動化を始める前に、以下の項目を準備しておきましょう。 MCPをサポートするエディタのインストール(今回はCursorを使用) Googleアカウント 自動化したいGoogleサービス(DriveやSheetsなど) MCP Server( https://github.com/xing5/mcp-google-sheets ) ←本記事ではこれを利用します。 簡単な自然言語での命令文を考える準備 Google Cloud Platform (GCP)の設定 まず、Google Sheets APIを利用するためにGCP側の設定を行います。 1-1. プロジェクトを作成 GCP コンソールにアクセスし、新しいプロジェクトを作成します。 ![Create Project1](/assets/blog/authors/jongseok/mcp/mcp_01_01.png =1080x) ![Create Project2](/assets/blog/authors/jongseok/mcp/mcp_01_02.png =360x) 1-2-1. サービスアカウントを作成 AIがGoogle Sheetsにアクセスするための「サービスアカウント」を作成します。 ![Service Account1](/assets/blog/authors/jongseok/mcp/mcp_02_01.png =1080x) Service Accountsの+Create service accountを押します。 ![Service Account2](/assets/blog/authors/jongseok/mcp/mcp_02_02.png =360x) Service account nameを入力してDoneを押します。 1-2-2. Permission追加 ![Service Account3](/assets/blog/authors/jongseok/mcp/mcp_02_03.png =1080x) Permission→Manage access→RoleをEditorにしてSaveを押します。 1-2-3. Keys追加 ![Service Account4](/assets/blog/authors/jongseok/mcp/mcp_02_04.png =1080x) Keys→Add Keyに押してJSONを選択してCreateします。 そうしたら認証ファイルがDownloadされます。 ![Service Account5](/assets/blog/authors/jongseok/mcp/mcp_02_05.png =1080x) ファイルの中身のこんな感じになります。 これでサービスアカウント発行は終わりです。 このファイル client_email は後で使います。 1-3. Googleサービスを設定 ![Service Account5](/assets/blog/authors/jongseok/mcp/mcp_03_01.png =1080x) APIS & ServicesのLibraryを押します。 ![Service Account5](/assets/blog/authors/jongseok/mcp/mcp_03_02.png =1080x) 今回利用したいServiceが見えますね。 Drive Sheets 二つのServiceをEnableを押します。 2-1. GoogleDriveの設定 ![Google Drive1](/assets/blog/authors/jongseok/mcp/mcp_04_01.png =1080x) Google Driveに入ってAIを利用するFolderを作ります。 例)Sheets For MCPで作りました → その後ShareのShareを押します。 ![Google Drive2](/assets/blog/authors/jongseok/mcp/mcp_04_02.png =360x) 上の欄には 1-2. サービスアカウントを作成 したJSONファイルに中にある client_email をcopyして入力します。 ![Google Drive3](/assets/blog/authors/jongseok/mcp/mcp_04_03.png =360x) 追加されたか確認してDoneを押します。 2-2. Folder IDを取得 ![Google Drive4](/assets/blog/authors/jongseok/mcp/mcp_04_04.png =1080x) 設定が終わったら Folder ID を取得します。 これで設定に必要な物は全部準備できました。 実際に設定して使ってみましょう! MCP設定(Cursor&Terminal) Windowsでも可能ですが、ここではMacを基準に説明します。 MCPのセットアップには簡単なCloudServerを利用する方法もありますが、今回はLocalServerでやってみます。 まずはuv,uvxが必要なのでTerminalでインストールしましょう。 uvx は、高速なPythonパッケージインストーラとレゾルバである uv の一部らしいです。 1. uv,uvxインストール & 環境変数セット curl -LsSf https://astral.sh/uv/install.sh | sh ![Terminal1](/assets/blog/authors/jongseok/mcp/mcp_05_01.png =1080x) 上手くインストールできました。 ご自身の環境に合わせて値を入力してください。 export SERVICE_ACCOUNT_PATH="/path/to/your/service-account-key.json" export DRIVE_FOLDER_ID="YOUR_DRIVE_FOLDER_ID" 準備した二つ SERVICE_ACCOUNT_PATH, DRIVER_FOLDER_ID を環境変数でセットします。 2. Project設定 git clone https://github.com/yourusername/mcp-google-sheets.git cd mcp-google-sheets ProjectをCloneし、Cloneしたディレクトリに移動します。 どこに置いても問題ありませんが、管理しやすくするため、発行された service_account.json ファイルはProjectの下に移動しました。 ![Terminal2](/assets/blog/authors/jongseok/mcp/mcp_05_02.png =360x) サーバーを起動してみましょう。 uv run mcp-google-sheets ![Terminal3](/assets/blog/authors/jongseok/mcp/mcp_05_03.png =1080x) これでLocalのサーバーは準備できました。 最後に使うためにMCPを設定しましょう。 3. MCP設定 ![Cursor1](/assets/blog/authors/jongseok/mcp/mcp_06_01.png =1080x) MCP設定するためには右上にある⚙️を押します。 ![Cursor2](/assets/blog/authors/jongseok/mcp/mcp_06_02.png =1080x) MCP項目中にある+Add new global MCP Serverを押します。 ![Cursor3](/assets/blog/authors/jongseok/mcp/mcp_06_03.png =1080x) 入力画面が表示されます。 { "mcpServers": { "mcp-google-sheets-local": { "command": "uv", "args": [ "run", "--directory", "/Users/jongseok.bae/MCP/GoogleServices/mcp-google-sheets", "mcp-google-sheets" ], "env": { "SERVICE_ACCOUNT_PATH": "/Users/jongseok.bae/MCP/GoogleServices/mcp-google-sheets/service_account.json", "DRIVE_FOLDER_ID": "1n4HUOiglwjiTKHcw8jSATYcPtCRqNn6O" } } } } args にはご自身のProjectがあるPathを入力 env にはSERVICE_ACCOUNT_PATH,DRIVE_FOLDER_IDを入力 上記の値を入力して保存します。 ![Cursor4](/assets/blog/authors/jongseok/mcp/mcp_06_04.png =1080x) 🟢が点灯していれば、正しくセットアップされています。 このMCPで利用可能な機能はTools:list_spreadsheets,create_spreadsheet,get_sheet_data...など表示されます。 これでセットアップは完了です。お疲れさまでした! それでは実際に、どのように自然言語で命令を出して、Google Sheets に自動入力を行うのかを試してみましょう。 実際に使ってみる 準備が整ったところで、いよいよ本番です。 例えば、以下のように自然言語で指示を出すだけで… https://news.google.com/home?hl=en-US&gl=US&ceid=US:en 「本日のGoogleニュース」 という名前の新しいGoogleスプレッドシートを作成してください。 掲載されているトップニュースを、タイトル、概要、URLの3列で整理してシートに追加してください。 実際には裏側で、次のような流れが走っています: Cursorで命令を入力 MCP Serverが命令を受信 自然言語を解析 → Google Sheets APIに変換 自動でスプレッドシート作成 + データ挿入 ![Cursor4](/assets/blog/authors/jongseok/mcp/mcp_06_05.png =720x) MCP実行する中身を見るとこんな感じになってます。(自然言語を解析して処理を実行) ![demo2](/assets/blog/authors/jongseok/mcp/demo2.gif =1080x) 結果として、上で紹介したようなスプレッドシートが生成されました! まとめ 今回紹介したMCPとGoogle Sheetsの連携は、単なる技術紹介にとどまるものでがありません。 「自然言語一文で業務を動かす」という体験は、 もはや未来の話ではなく、 今この場で実現可能なこと です。 もちろん、 セキュリティやアクセス制御など、まだ検討すべき課題もあります。 しかし、小さな自動化から始めていくことで、業務効率化の可能性は十分に感じられました。 もし業務の自動化を考えている方は、今回の事例を参考に、ぜひ一度試してみてください。 想像よりも早く、変化を実践できるかもしれません。
アバター
本記事は KINTOテクノロジーズアドベントカレンダー2024 の24日目の記事です🎅🎄 はじめに こんにちは、 Rasel です。現在、KINTOテクノロジーズ株式会社でAndroidエンジニアとして働いています。今日は、Kotlin Multiplatform (KMP) でのテストへのアプローチについて簡単に紹介します。 KMPでのクロスプラットフォームテストにより、AndroidとiOS間で共有されるコードの信頼性を確保できます。シンプルなテストは素晴らしい開始点ですが、多くのアプリケーションでは、より複雑なセットアップを行って、ビジネスロジックの検証、非同期関数の処理、コンポーネント間の相互依存性のテストなどを行う必要があります。 この投稿では、非同期コードの処理、依存関係のテスト、パラメーター化されたテストの使用、複雑なセットアップの構築など、高度なテストシナリオについて説明します。詳細を掘り下げてみましょう! 高度なクロスプラットフォームテストの設定 共通のテスト依存関係を構成する こちらは、アサーション、コルーチン、モック用のテストライブラリを使用した強化されたセットアップです。 // In shared module’s build.gradle.kts plugins { id("dev.mokkery") version "2.5.1" // Mocking library for multiplatform } sourceSets { val androidInstrumentedTest by getting { dependencies { implementation(libs.core.ktx.test) implementation(libs.androidx.test.junit) implementation(libs.androidx.espresso.core) implementation(libs.kotlin.test) } } commonTest.dependencies { implementation(libs.kotlin.test) implementation(libs.kotlinx.coroutines.test) implementation(libs.kotlin.test.annotations.common) } iosTest.dependencies { implementation(libs.kotlin.test) } } このセットアップにより、非同期テストや、テストに必要な依存関係のモック化を処理できるようになります。 KMPにおける基本的なテスト KMPを使用すると、 commonTest で共有テストを簡単に記述できると同時に、 androidTest と iosTest でプラットフォーム固有のテストが可能になります。開始方法の概要は次のとおりです。 class GrepTest { companion object { val sampleData = listOf( "123 abc", "abc 123", "123 ABC", "ABC 123", ) } @Test fun shouldFindMatches() { val results = mutableListOf<String>() // Let's get the matching results with our global function grep which is defined in commonMain module grep(sampleData, "[a-z]+") { results.add(it) } // Check results with expectations assertEquals(2, results.size) for (result in results) { assertContains(result, "abc") } } } commonTest におけるこの簡単なテストを使用すると、プラットフォーム間で commonMain モジュールの共有ロジックを検証できます。 高度なテストシナリオ 1.コルーチンを使用した非同期関数のテスト 多くの共有KMPプロジェクトでは、非同期作業にコルーチンを使用します。コルーチンを効果的にテストするには、テストでコルーチンの実行を制御および進行を制御できるツールである kotlinx-coroutines-test を使用する必要があります。 サンプルシナリオは次のとおりです。ユーザーデータを非同期的に取得するUserRepositoryを持っているとします。データ検索をシミュレートするためにコルーチンフローを制御するテストを記述します。 @OptIn(ExperimentalCoroutinesApi::class) class UserRepositoryTest { private val testDispatcher = StandardTestDispatcher() private val userRepository = UserRepository(testDispatcher) @BeforeTest fun setUp() { Dispatchers.setMain(testDispatcher) // Set test dispatcher as main } @AfterTest fun tearDown() { Dispatchers.resetMain() // Reset main dispatcher } @Test fun `fetch user successfully`() = runTest { val user = userRepository.fetchUser("123") assertEquals("John Doe", user.name) } } この例では、 Dispatchers.setMain(testDispatcher) がデフォルトのディスパッチャを置き換えることで、コルーチンの実行を制御できるようにします。 runTest {} は、コルーチンのセットアップとクリーンアップを自動的に処理し、サスペンド関数の管理を容易にします。 runTest 内のフローを制御して、遅延やその他の非同期動作をシミュレートできます。 2.モックの使用と相互関係の検証 マルチプラットフォームセットアップで依存関係をモックすると、クラス間の複雑な相互作用のテストを効率化します。ここでは、APIクライアントをモックしてネットワーク呼び出しをシミュレートし、リポジトリとのやり取りを確認します。 class NewsRepositoryTest { private val testDispatcher = StandardTestDispatcher() private val apiService = mock<ApiService> { everySuspend { getNews(defaultSectionName) } returns TopStoryResponse(mockNewsArticles) } private val repository = NewsRepository(apiService, testDispatcher) @BeforeTest fun setUp() { Dispatchers.setMain(testDispatcher) // Set test dispatcher as main } @AfterTest fun tearDown() { Dispatchers.resetMain() // Reset main dispatcher } @Test fun `fetch news list for home section successfully`() = runTest { val articleList:List<Article> = repository.fetchNews() assertEquals(mockNewsArticles.size, articleList.size) assertEquals(mockNewsArticles.first(), articleList.first()) assertEquals(mockNewsArticles.last(), articleList.last()) verifySuspend { apiService.getNews(defaultSectionName) } } } const val defaultSectionName = "home" この例では、 mock<ApiService>() を使用すると、明示的な動作定義がなくてもモックがデフォルト値を返すことができます。 verifySuspend は、 getNews() が適切に呼び出されたかどうかを確認し、リポジトリ内の正しいフローを確保します。 everySuspend は、 getNews() の呼び出しごとに戻されるモックデータを設定します。 NewsRepository の getNews() メソッドは、 ApiService から TopStoryResponse としてデータを取得し、結果を List<Article> として返します。 3.さまざまな入力シナリオに関するパラメーター化されたテスト KMPは現在、JUnit5などのライブラリと同様に、パラメーター化されたテストにネイティブに対応していません。ただし、Kotlinの機能を使用してパラメーター化されたようなテスト動作を実現する回避策があり、さまざまな入力を使用して関数を簡潔かつ再利用可能な方法でテストできます。以下は、DateUtils関数のさまざまな日付フォーマットをテストする例です。 class DateUtilsTest { private val testCases = listOf( Triple("2024-11-18T00:00:00", "dd MMM yyyy", "18 Nov 2024"), Triple("2024-11-18T15:34:00", "hh:mm", "03:34"), Triple("2024-11-18T15:34:00", "dd MMM yyyy hh:mma", "18 Nov 2024 03:34PM"), ) @Test fun `check date format`() { testCases.forEach { (dateString, outputFormat, expectedOutput) -> val actualOutput = formatDatetime(dateString = dateString, inputFormat = "yyyy-MM-dd'T'HH:mm:ss", outputFormat = outputFormat) assertEquals(expectedOutput, actualOutput, "Failed for date $dateString and output format $outputFormat") } } } [!注記] ここで、 formatDatetime() は、日付文字列をフォーマットするために DateUtils 内で定義されたグローバル関数です。この例では、 入力ごとに予想される結果を定義して、コードを重複させることなくテスト範囲を簡単に拡張できます。 プラットフォーム固有のテストライブラリに依存せずに、 androidTest と iosTest の両方で動作します。 複雑なセットアップによるプラットフォーム固有のテスト 一部のテストケースでは、特にAndroidの SharedPreferences またはiOSの NSUserDefaults を使用する場合、プラットフォーム固有のAPIまたは動作を使用する必要があります。両方のプラットフォームの内訳は次のとおりです。 Android固有のテスト:SharedPreferencesのテスト Android では、 SharedPreferences を使用してデータストレージをテストする必要がある場合があります。AndroidXの ApplicationProvider を使用してテストコンテキストにアクセスするセットアップを次に示します。 @RunWith(AndroidJUnit4::class) class SharedPreferencesTest { private lateinit var sharedPreferences:SharedPreferences private val expectedAppStartCount = 10 @BeforeTest fun setUp() { val context = ApplicationProvider.getApplicationContext<Context>() sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) } @AfterTest fun tearDown() { sharedPreferences.edit().clear().apply() } @Test fun checkAppStartCountStoresCorrectly() { sharedPreferences.edit().putInt(KEY_APP_START_COUNT, expectedAppStartCount).apply() val actualAppStartCount = sharedPreferences.getInt(KEY_APP_START_COUNT, -1) assertEquals(expectedAppStartCount, actualAppStartCount) } companion object { private const val SHARED_PREF_NAME = "test_prefs" private const val KEY_APP_START_COUNT = "app_start_count" } } このプラットフォーム依存的なテストは、 androidInstrumentedTest 内に配置する必要があり、実行するにはエミュレーターまたは実際のAndroidデバイスが必要になります。 iOS固有のテスト:NSUserDefaultsのテスト iOSでは、同様のロジックを NSUserDefaults でテストできます。通常、このテストは、以下に示すように iosTest において記述します。 class NSUserDefaultsTest { private lateinit var userDefaults:NSUserDefaults private val expectedAppStartCount = 10 @BeforeTest fun setUp() { userDefaults = NSUserDefaults.standardUserDefaults() } @AfterTest fun tearDown() { userDefaults.removeObjectForKey(KEY_APP_START_COUNT) // Clean up after test } @Test fun `test app start count stores correctly in NSUserDefaults`() { userDefaults.setObject(expectedAppStartCount, forKey = KEY_APP_START_COUNT) val actualAppStartCount = userDefaults.integerForKey(KEY_APP_START_COUNT) assertEquals(expectedAppStartCount, actualAppStartCount.toInt()) } companion object { private const val KEY_APP_START_COUNT = "app_start_count" } } 複雑なテストセットアップ:相互依存関係のテスト 高度なテストでは、相互に作用する複数の依存関係をシミュレートする必要がある場合があります。以下は、API クライアントとローカルキャッシュの両方に依存するリポジトリが関与する例です。 class ComplexRepositoryTest { private val apiService = mock<ApiService>() private val localCache = mock<LocalCache>() private val repository = ComplexRepository(apiService, localCache) @Test fun `fetch data from local cache`() = runTest { everySuspend { localCache.getArticles() } returns mockNewsArticles val data = repository.getNewsArticles() assertEquals(mockNewsArticles.size, data.size) verifySuspend { localCache.getArticles() } } @Test fun `fetch data from remote source`() = runTest { everySuspend { localCache.getArticles() } returns emptyList() everySuspend { apiService.getNews(defaultSectionName) } returns TopStoryResponse(mockNewsArticles) everySuspend { localCache.insertAllArticles(mockNewsArticles) } returns Unit val data = repository.getNewsArticles() assertEquals(mockNewsArticles.size, data.size) verifySuspend { localCache.getArticles() apiService.getNews(defaultSectionName) localCache.insertAllArticles(mockNewsArticles) } } } この例では、 リポジトリは最初にキャッシュをチェックし、キャッシュが空の場合にのみAPIを呼び出します。 私たちは相互作用を検証することで、リポジトリが意図したフローに従い、不必要にAPIを呼び出さないようにしています。 上記のすべてにより、共通、Android、およびiOS実装のテストの記述が完了しました。これらのテストを個別に実行することも、 allTests という名前のGradleタスクを使用してすべてのテストを一度に実行することもできます。これにより、プロジェクト内のすべてのテストが、対応するテストランナーで実行されます。![Gradle task allTests](/assets/blog/authors/ahsan_rasel/2024-12-24-testing-kmp/gradle-all-test-task.png =800x) テストを実行するたびに、 shared/build/reports/ ディレクトリ内にHTMLレポートが生成されます。 ![Test report](/assets/blog/authors/ahsan_rasel/2024-12-24-testing-kmp/test-report.png =550x) このようにして、当社のKMPの取り組みを進めながら、適切に調整されたテストでアプリの品質を確保することができます。 複雑なKMPテストのベストプラクティス テストセットアップを分離する :ヘルパー関数やクラスを作成してモックの設定を行い、繰り返しコードを削減します。 非同期動作を制御する :kotlinx-coroutines-testを使用して、コルーチンのタイミングを制御し、遅延やネットワーク応答をシミュレートします。 クロスプラットフォームテストとプラットフォーム固有のテストをミックスする :ネイティブ動作にはプラットフォーム固有のテストを使用し、共有ロジックにはクロスプラットフォームテストを使用します。 テスト範囲をモニターする :特に、依存関係のある複雑なフローについては、包括的なカバレッジを確保します。 結論 Kotlin Multiplatformにおけるクロスプラットフォームテストでは、単純なテストシナリオと複雑なテストシナリオを処理でき、AndroidとiOS全体でコードの信頼性を確保するための強力なツールが得られます。再利用可能なテストをセットアップし、非同期動作を制御し、依存関係を分離することで、重要なビジネスロジックを検証して全体的なコード品質を向上させる高度なテストを記述することができます。 これらの手法により、KMPプロジェクトはプラットフォーム間で一貫性のある信頼性の高いパフォーマンスを発揮できるようになり、あらゆる場所のユーザーにシームレスな体験を提供できるようになります。KMPを楽しんでください!
アバター
はじめに こんにちは!Yao Xie です。 KINTOテクノロジーズのモバイルアプリ開発グループで、 Android の KINTO かんたん申込みアプリ を開発しています。本記事では、Compose Multiplatform とネイティブの UI フレームワークの両方を活用したハイブリッドモデルを実装する方法について、私の考えをいくつか共有したいと思います。 モバイルアプリの開発では、限られた予算や多様なチーム規模、厳しい納期などに直面しつつ、長期的な保守性と一貫したユーザー体験の確保が求められる場合が多くあります。Kotlin Multiplatform (KMP) 上に構築されたハイブリッド アーキテクチャには、コア UI フレームワークとして Compose Multiplatform が組み込まれ、iOS の SwiftUI や Android の Jetpack Compose といったネイティブフレームワークと組み合わせることで、そういった課題に柔軟に対応することができます。少ない予算で MVP を素早く立ち上げる場合でも、完成度の高いアプリへとスケールアップする場合でも、このアプローチなら制約に適応でき、コード品質も長期的に維持できます。 さまざまな制約に対応できる、柔軟で保守しやすいアプローチ 一貫性を保ちながらの迅速な開発 限られた予算、小規模なチーム、タイトなスケジュールといった制約のあるプロジェクトにおいて、私が感じている主なメリットは次のとおりです。 共有ビジネスロジック: ネットワーク処理、データモデル、ビジネスルールといったコア機能を KMP のモジュールに一度記述することで、Android と iOS の両方で一貫した動作が保証され、コードの重複を避けつつ将来的なメンテナンスが楽になります。 Compose Multiplatform による共有 UI:Compose Multiplatform は Kotlin Multiplatform エコシステムの一部で、共有 Kotlin コードを基盤にして自然なかたちで成り立ち、UI コンポーネントを構築します。UI の大部分を、共用のコンポーザブルなコンポーネントとして開発します。Android では Jetpack Compose を通じてそのまま活用でき、iOS でも ComposeUIViewController などのヘルパーを使ってこれらのコンポーネントを組み込むことが可能です。このように統一されたアプローチを取ることで、両プラットフォームにおいて共通の設計・動作ガイドラインに従うことができ、長期的なサポートも容易になります。 この戦略で、一貫性を維持しながら開発時間を最小限に抑えることができます。高品質なユーザーエクスペリエンスの提供と将来を見据えたコード設計にとって、この要素は重要なポイントとなります。 Android ロボットは、Google が作成および提供している作品から複製または変更したものであり、クリエイティブ・コモンズ表示 3.0 ライセンスに記載された条件に従って使用しています。* ネイティブ拡張によるスケールアップ プロジェクトに多くの資金、人員、追加の時間が確保できるようになると、次のようなことが可能になります。 段階的なネイティブ統合:強固な共有基盤があれば、ネイティブの UI 要素を使用して主要な画面を徐々に置き換えたり、改善したりすることができますたとえば、SwiftUI で iOS の重要な画面を強化し、プラットフォーム固有のデザイン標準により適した形に仕上げられます。 expect/actual メカニズム:Kotlin の expect/actual パターンを使用することで、プラットフォーム固有のボタンなどといった汎用的な共有コンポーネントを定義し、それぞれのネイティブ実装を提供できます。こうすると、まずは共有 UI で開発を始め、洗練されたネイティブの改良を後から一番重要な箇所に限定して加えることができるようになります。 多様なチーム規模とプロジェクトのタイムラインに対応 ハイブリッドアプローチは、プロジェクトの成長に応じて臨機応変に対応できるように設計されています。 小規模なチームやタイトなスケジュールの場合:コードの再利用を最大限に活用することが焦点になります。少人数のチームでも共有のビジネスロジックや UI を構築・維持することができ、一貫性を保ちつつ、市場投入までのスピードを加速できます。 大規模なチームや開発期間に余裕がある場合:チームが拡大したりスケジュールに余裕ができたら、ネイティブコンポーネントの開発に追加のリソースを投入して、ユーザーエクスペリエンスを向上させます。こういった段階的な戦略を取ることでリスクを最小限に抑え、コアロジックの安定性と保守性を長期にわたって確保できます。 共有ロジックと UI の一貫性をプラットフォーム間で保つと、複雑さと技術的な負債が軽減され、プロジェクトの保守や拡張がもっと楽になります。 Kotlin Multiplatform ハイブリッドモードを示すコードスニペット 共有ビジネスロジック この共有モジュールでは、Android アプリと iOS アプリの両方で同じコアロジックを利用することができます。このため、一貫性が保たれてメンテナンスにかかるオーバーヘッドが軽減されます。 // Api interface Api { suspend fun getUserProfile():User } class ApiImpl(private val client:HttpClient) :Api { override suspend fun getUserProfile():User { return client.get { url { protocol = URLProtocol.HTTPS host = "yourdomain.com" path("yourpath") } }.safeBody() } } // Repository interface UserRepository { suspend fun getUserProfile():Flow<User> } class UserRepositoryImpl(private val api:Api) :UserRepository { override suspend fun getUserProfile():Flow<User> { return flowOf(api.getUserProfile()) } } // ViewModel class ProfileViewModel( private val repository:UserRepository, ) :ViewModel() { private val _uiState = MutableStateFlow(ProfileUiState()) val uiState:StateFlow<ProfileUiState> = _uiState.asStateFlow() @OptIn(ExperimentalCoroutinesApi::class) fun onAction(action:ProfileScreenAction) { when (action) { is ProfileScreenAction.OnInit -> { viewModelScope.launch { // Set loading state to true _uiState.update { it.copy(isLoading = true) } // Retrieve authorization info and then fetch user profile data repository.getAuthorizationInfo() .flatMapLatest { authInfo -> // If authInfo exists, call getUserProfile() to get user data; otherwise, emit null if (authInfo != null) { repository.getUserProfile() } else { flowOf(null) } } // Catch any exceptions in the Flow chain .catch { e -> e.printStackTrace() } // Collect the user profile data and update the UI state .collect { userProfile -> _uiState.update { state -> state.copy( isLoading = false, data = userProfile, errorMessage = if (userProfile == null) "User profile data is null" else null ) } } } } } } } Compose Multiplatform による共有 UI Kotlin Multiplatform に統合されている Compose Multiplatform は、共有の Kotlin コード上で UI コンポーネントの作成が可能です。 @Composable fun ProfileScreen( viewModel:ProfileViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState().value LaunchedEffect(Unit) { viewModel.onAction(ProfileScreenAction.OnInit) } Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .background(White) .padding(16.dp) ) { // Profile header with image and user details Row( modifier = Modifier .fillMaxWidth() .background(White, shape = RoundedCornerShape(16.dp)) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { AsyncImage( model = uiState.data.profileImage, contentDescription = "Profile Image", modifier = Modifier .size(64.dp) .clip(CircleShape) ) Spacer(modifier = Modifier.width(16.dp)) Column { Text( text = uiState.data.userName, fontSize = 16.sp, color = Color.Black ) Text( text = uiState.data.email, fontSize = 14.sp, color = Color.Blue.copy(alpha = 0.6f) ) } } Spacer(modifier = Modifier.height(16.dp)) MenuLabel(label = "Account Settings") .... } } リーンアプローチ:このコンポーザブルを両プラットフォームで直接使用すると、外観と動作の一貫性が維持できて便利だと思います。 スケールアプローチ:基盤となるビジネスロジックには手を加えず、必要に応じてネイティブコンポーネントを使用して UI を強化します。 expect/actual 経由のネイティブ UI expect コンポーネントを定義します。 @Composable expect fun PlatformButton( modifier:Modifier = Modifier, label:String, onClick: () -> Unit ) Android 向けの実装 (androidMain) @Composable actual fun PlatformButton( modifier:Modifier, label:String, onClick: () -> Unit ) { Button( onClick = onClick, modifier = modifier ) { Text(text = label) } } iOS 向けの実装 (iosMain) @Composable actual fun PlatformButton( modifier:Modifier, label:String, onClick: () -> Unit ) { UIKitView( modifier = modifier, factory = { // Create a native UIButton instance. val button = UIButton.buttonWithType(buttonType = 0) button.setTitle(label, forState = UIControlStateNormal) // Style the button ... // Create a target object to handle button clicks. val target = object :NSObject() { @ObjCAction fun onButtonClicked() { onClick() } } // Add the target with an action selector. button.addTarget( target = target, action = NSSelectorFromString("onButtonClicked"), forControlEvents = UIControlEventTouchUpInside ) button } ) } ブレンド UI:Flutter に対する大きな強み Flutter と異なり、Compose Multiplatform は1 つの画面上で共有 UI とネイティブコンポーネントをシームレスに組み合わせることをサポートしてくれます。 共有 UI からネイティブ UI へ完全に切り替える必要のない場合が多く、1つの画面上で両者を組み合わせることができます。このアプローチで、画面全体をどちらか一方のフレームワークに完全に任せることなくCompose Multiplatform とネイティブフレームワークそれぞれの強みを活かすことができます。たとえば、iOS で SwiftUI のネイティブリストと並べて共有の コンポーザブルヘッダーを表示したいとします。 ブレンド UI は、次のように構築できます。 ネイティブレイアウトに共有コンポーザブルを埋め込む Compose Multiplatform の強みのひとつは、共有 Compose UI コンポーネントをネイティブ SwiftUI レイアウト内にシームレスに埋め込めることです。開発者は画面全体を書き直さなくとも、共有コンポーネントを段階的に導入することができます。この柔軟性は、Flutter のオーバーレイ方式では実現が難しいものです。 たとえば、SwiftUI をコンテナとして使用して、その中に UIViewController でラップした Compose Multiplatform の共有 UI を埋め込むことができます。 import SwiftUI import shared // Import the shared KMP framework struct ComposeContainerView:UIViewControllerRepresentable { let user:User func makeUIViewController(context:Context) -> UIViewController { // Use the Compose wrapper to create the UIViewController return AppComposeUI().createProfileVC(user) } func updateUIViewController(_ uiViewController:UIViewController, context:Context) { } } struct ContentView:View { let user:User var body: some View { VStack { Text("Native SwiftUI Header") .font(.headline) .padding() // Embed the Compose UI inside SwiftUI. ComposeContainerView(user: user) .frame(height:250) List { Text("Native SwiftUI Item 1") Text("Native SwiftUI Item 2") } } } } Compose Multiplatform 画面にネイティブ iOS UI を 埋め込む Compose Multiplatform では、共有 Compose 画面内に SwiftUI や UIKit などのネイティブ iOS ビューを埋め込むことも可能です。これも、Flutter では簡単に実現できない機能のひとつです。 埋め込む SwiftUI ビューを定義します。 import SwiftUI struct MySwiftUIView:View { let user:User var body: some View { VStack { Text("Native SwiftUI Content") .font(.body) Text("Welcome, \(user.firstName)") } .padding() .background(Color.gray.opacity(0.2)) .cornerRadius(8) } } 次に、UIKitView を使用してこのネイティブ ビューを共有 Compose UI に統合します。 @Composable fun ComposeWithNativeUI(user:User) { Column { // Shared Compose UI header. ProfileHeader(user = user) Spacer(modifier = Modifier.height(16.dp)) // Embed a native iOS view using UIKitView. UIKitView(factory = { // Create a UIHostingController with a SwiftUI view. val hostingController = UIHostingController(rootView = MySwiftUIView(user = user)) hostingController.view }, modifier = Modifier.fillMaxWidth().height(100.dp)) } } この双方向のインライン統合により、Compose Multiplatform が Flutter と明確に差別化されます。これでネイティブのルックアンドフィールを保ちながら、共有 UI コンポーネントを段階的に導入できるようになります。共有コードの効率性とネイティブ UI の豊かさ・品質を兼ね備えているので、開発者は柔軟に対応でき、大規模な書き換えリスクも抑えられます。 Compose Multiplatform は、共有 UI とネイティブプラットフォーム UI をより柔軟できめ細かく統合できます。この点で、Flutter よりも大きな強みがあります。Flutter は通常、オーバーレイ方式で共有 UI コンポーネントをレンダリングします。それとは異なり、Compose Multiplatform はインラインレンダリングを採用していて、共有 UI とネイティブ UIの要素を自然に組み合わせることができます。 長所 柔軟性、保守性、拡張性のベストプラクティス ラピッドプロトタイピングを取り入れる ユーザーがまだアプリに触れていない段階で、ピクセル単位の細部を完璧に仕上げようと何週間も費やすのは、リスクが高いことだと痛感しました。それを痛感しました。最近では、私たちのチームは 1〜2 週間で共有 UI のプロトタイプを作成し、関係者に配布して、早期にフィードバックを収集するようにしています。アイデアに手応えを感じたら、それまでに作ったものは捨てずに、ネイティブの装飾(アニメーション、プラットフォーム固有のジェスチャーなど)を重ねていきます。 段階的な開発を計画する 私たちの経験則は、「まずは小さくリリース、後からリファクタリング」です。最初は、コアロジック用の Kotlin Multiplatform モジュールと単独の共有 Compose 画面からスタートします。その構成で上手く動くことが確認できたら、次の画面の実装に進んだり、共有ウィジェットを SwiftUI や Jetpack Compose のバージョンに置き換えたりと、作業を1つずつ進めます。徐々に進めるやり方で、開発スピードを保ち、書き直しを避けることができます。 リソース、品質、スケーラビリティのバランスをとる チームがまだ2人だけだった頃は、UI の9割を共有コードで作っていました。それが、限られた体制でできる精一杯の方法だったからです。メンバーが増えてデザインへのこだわりも強くなるにつれて、重要なフローはネイティブ実装に切り替え、その他の部分は共有コードのままにしました。このように段階的に移行したことで、バックログを現実的な状態に保ち、リリースに支障をきたすことなく品質を高めることができました。 一貫性を保つ ビジネスロジックを1つの Kotlin モジュールに集約することで、マージに伴う悩みの種が数え切れないほど軽減されました。1回のバグ修正で、すべてのプラットフォームに反映されます。「iOS は直ったけど、Android はまだバグってる」のように取り残されることは、もうありません。派手さはありません。ですが、この共通コアがあるからこそ、チームは締め切り直前の夜でも安眠することができます。 変化への適応力を確保する 複数のプラットフォームにまたがるプロジェクトを率いた経験から言えるのは、成功するチームはアーキテクチャを静的な設計図ではなく、生き物のような存在として扱っています。予算が厳しくなったり、スケジュールが変わったり、突然新しいスキルを持つメンバーが加わっても対応できるよう、すべてのレイヤーを柔軟に設計しています。Compose Multiplatform を使用することで共有 UI とネイティブ UI をオンデマンドで組み合わせられるので、「MVP モード」から「ネイティブ仕上げモード」へ、数ヶ月とかからずに数日で切り替えることができます。製品戦略や市場が一夜にして変わったときも、この臨機応変さには何度も助けられてきました。 結論 Kotlin Multiplatform と Compose Multiplatform を基盤に、ネイティブ UI フレームワークと併用して構築されたこの適応力の高いハイブリッドアーキテクチャは、他に類を見ない柔軟性を備えています。このハイブリッドアーキテクチャがあれば、一貫性と長期的な保守性を維持しつつ、予算、チーム規模、スケジュールといった制約に応じて開発戦略を柔軟に調整できます。小規模なプロジェクトでは、コードの再利用を最大限に活用してスピーディに立ち上げましょう。そしてリソースが増えてきたら、コアロジックはそのままに、重要な画面をネイティブ UI で徐々に強化します。 重複の削減、メンテナンスの効率化に、柔軟で段階的なこのモデルを取り入れてみてください。そしてプロジェクトの成長に応じて拡張していける、一貫性のある高品質なユーザーエクスペリエンスを実現してください。
アバター
Self-introduction Nice to meet you! I'm Tsuji, a manager in the Cloud Infrastructure Group at KINTO Technologies. I joined the company back in May 2020, so I've been around for a while. Since graduating, I've been working as an infrastructure engineer, handling both on-prem and cloud systems. Overview GitHub Copilot Agent Mode for VSCode officially launched recently (as of April 16, 2025), I'll refer to it as "Agent" from here. Seems like the perfect excuse to try building something useful with it. Premise I'm an infra engineer through and through. Frontend? Backend? Not really my thing. But when it comes to infra—especially AWS—I can hold my own. This article won't dive deep into technical stuff. What I Want to Do See how far infra-only engineers can go using Agents. Develop a practical system we can actually use in our daily work, as an MVP (Minimum Viable Product). Absolutely no writing code by hand. So... What Should We Build? Basically, all of KINTO Technologies' infrastructure is created and managed with IaC (Terraform). If you have used Terraform before, you know the plan output can be hard to read unless you're used to it, right? So this time I'm aiming to create something that makes it easier to view Terraform plans. What I Want to Make Make Terraform plans easier to read: I might as well throw in some generative AI for summaries. Since the Agent's building it, why not have it create a slick frontend too? Development Environment *Here's the development environment used this time just for reference: Items What I used OS Linux(WSL2) IDE VSCode(1.99.3) Agent GitHub Copilot Agent Generative AI Model Claude 3.7 Sonnet Infra AWS Let's Give It a Go First things first: we need a prompt. Yep, that's where it all starts. This time, I gave the Agent a super rough idea of what I wanted using the following prompt: I'd like to implement a mechanism to view the result of Terraform plan in the browser. AWS configuration: CloudFront + S3 + Lambda + Bedrock Planned workflow: 1. Run Terraform plan -out foo.plan locally and output the plan contents to a file 2. Upload foo.plan to S3. 3. A lambda is triggered, runs Terraform show foo.plan, converts it into a human-readable format, and stores it to S3 4. Bedrock analyzes the result of Terraform show and generates a summary of the plan 5. Users can view both the summary and full plan content via a browser. I'm planning to proceed with this approach. If you have a better suggestion, I'm happy to hear it. If not, please help me build the above. If any code needs to be generated, please create a directory under /home/mizuki/ and save the files there. Got a better prompt in mind? No need to get too deep into the details. I'm just an infra engineer here. But then the brilliant Agent took my idea, leveled it up by 300% and made it happen. You're looking to implement a system to view the result of a Terraform plan in a browser. The proposed idea is feasible and well thought out. First, I will evaluate the plan, suggest a few improvements, and then help with the implementation. # Evaluation of the current plan 1. Run Terraform plan -out foo.plan locally and save the output to a file 2. Upload foo.plan to S3 3. An S3 event triggers a Lambda function, which runs Terraform show foo.plan and outputs the result in a human-readable version to S3. 4. Bedrock analyzes the result of Terraform show and generates a summary of the plan 5. Users access the summary and full plan details via a browser # Possible improvements 1. **Security enhancement**: Restrict access to S3 and configure IAM permissions appropriately 2. **Visual representation of differences**: Visualize the plan result using graphs or colored text 3. **History management**: Save and compare past plan results 4. **Notifications**: Notify via Slack or email when important changes are detected 5. **Interactive UI**: Expand/collapse resource sections for easier browsing. Let’s move forward with these points in mind. First, the directory structure will be created. Next, we'll implement each component of this project. After creating a Terraform configuration file and define the necessary AWS resources. The directory structure has been created. Now, let's start implementing each component. Then, create `main.tf` to define the infrastructure using Terraform. This file will define the required resources such as the S3 bucket, CloudFront, Lambda, and IAM roles. : : (omitted) ... Wow, you're killing it. The Agent's killer feature is its ability to work locally. It even sets up a working directory and fills it with the Terraform files. After that, the Agent explains its actions and checks in with "Is it okay if I do this?" at each step. I have zero clue about Python, so I just kept mashing the "Accept" button every time it asked. In the end, it didn't just generate JavaScript, CSS, Python, and Terraform files, but also gave me a shell for building the Lambda. And on top of that? It included usage instructions, deployment steps, and suggestions for improvements. Time to Deploy It's awesome that the Agent made it for me—but… does it actually work??? No idea, but I'll go ahead and follow the deployment steps exactly as provided. Apply Terraform and create S3, CloudFront, etc. on AWS. Build it and create a zip for Lambda. Deploy the Lambda. Upload the JS and CSS files to S3. The deployment was completed without any errors. As Expected… Error. When I accessed the environment the Agent set up, got hit with a Lambda error. Not surprising, to be honest. I don't know anything about Python, there's no way to debug it myself. So... I'll just hand the Lambda logs over to the Agent and let it figure things out. Yes—just as I thought. It analyzed the logs, found the problem, and even fixed it. I redeployed the updated files, and this time, it actually worked. What Kind of System Did We End Up With? Here's what the final system architecture looks like! Here's how the whole thing works: The user sends Terraform plan results to S3. That triggers a Lambda function via an event. Lambda sends the plan results to Bedrock for analysis and summarization. Place the generated plan summary and the summary page's HTML file to S3. Step Functions deletes CloudFront cache. The user just drops the plan file into S3, and everything else happens automatically. Let's Try Accessing It Looks super cool! But hey, did it actually summarize the Terraform plan results like we originally set out to do? Perfect... The indentation's a little wonky here and there, but, let's not get hung up on the details. What's Good About It? The number of created, updated, and deleted resources is clearly displayed at the top for easy viewing. It explains the specific changes being made to each resource. A risk summary is provided to help you grasp what could go wrong with this plan. It even points out things you might want to improve going forward. This is exactly what I was hoping for. Add Detailed Requirements After that, I had the Agent make a few minor tweaks here and there: Timestamps are in UTC, change to JST. Fix indentations that are off. Attach a screenshot. Once the plan is generated, clear the CloudFront cache. Stuff like that. Even with all those detailed requests, the Agent handled everything without breaking a sweat . My Impressions After Trying It Building this system was way easier than I expected. I put it together in a single day, and honestly, most of that time was just waiting for the Agent to do its thing. The actual hands-on time was just about an hour. Even someone like me, who's only ever touched infrastructure, managed to build out both the frontend and backend solo. Now, to be fair—when I looked at the Python code, even I could tell there were some parts that didn't need to be there. Definitely room for improvement. But for something at the Minimum Viable Product (MVP) stage, this is more than good enough. From here, it'll be easy to optimize the code or expand the features as needed. The important thing is that we were able to create a functioning system in a short period of time. This allows us to quickly shape our ideas and find areas for improvement through actual operation. The key point is this: I was able to build a working system by myself in a short amount of time. I think Agent is the best tool for building an MVP (Minimum Viable Product) Summary It might still take some time before we can build full-scale enterprise systems using only the Agent, but even as a beginner in writing prompts, I was able to develop a working system with ease. What surprised me the most was how smoothly the Agent handled everything. All I had to do was keep clicking the "accept" button, and it built the system for me. Up until now, I was used to copying and pasting suggestions from generative AI and running commands manually. But this time, I barely touched the terminal. I just kept pressing "accept." I was able to create a system that can reduce workloads and prevent mistakes just by pressing a button. Also, the system I built is already in use, and I plan to fully integrate it into our operational workflow. Finally The Cloud Infrastructure Group I belong to is looking for people to join us. Whether you're confident in your cloud skills or just getting started and eager to learn, we'd love to hear from you. Please feel free to contact us. For more details, please check here and here (in Japanese). We’ve also posted a group introduction on Wantedly! You can read more about the Cloud Infrastructure Group here ! (in Japanese)
アバター
Hello, I am Ito, an Administrative Assistant in the Global Development Group at KINTO Technologies. My role is to support team members with various tasks including answering their inquiries and translating documents. I love cats and reading novels. Lying down and reading with my cat used to feel like heaven. But ever since my cat passed on to another heaven some time ago, I’ve been spending my days reading alone. I have read 5 or 6 English novels this year. Reading in English used to be nothing but a struggle, but lately, I’ve started to enjoy experiencing them in their original language. However, I think the most enjoyable part of speaking foreign languages is having conversations. If you speak in another language, not just English, you definitely open yourself up to more opportunities and choices. Of course, there are challenges along the way. What should you do to be able to speak foreign languages? Unlike children, who can absorb things effortlessly, most adults need to put in a significant amount of effort. Effort to create many opportunities to speak, based on understanding basic grammar and vocabulary. I think this is essential to mastering a language. In this article, I would like to introduce the Language Skill Up Project, organized by the Business Enhance Team of the Global Development Group, which aims to help improve the language skills of working adults. Introduction Currently, the Global Development Group has almost sixty members from diverse backgrounds, including various nationalities, cultures, and languages. Among them, some aren’t very proficient in Japanese, while others aren’t used to speaking English due to limited use in their previous roles. Although the members share strong development skills, it would be a loss for the company if language barriers prevented effective communication. To help address this issue, the Language Skill-Up Project was launched, led primarily by the Business Enhance Team, whose mission is to maximize the members' potential. The Ministry of Internal Affairs and Communications defines global human resources as individuals who possess language proficiency, communication skills, and cultural awareness. The Global Development Group offers an environment that fosters the development of these qualities. One of the goals of this project is to cultivate global human resources by making use of this environment. People from outside of the Group can also join our study sessions. The key to language learning is not telling yourself that you can’t do it. Our goal is to help learners build confidence by first becoming comfortable speaking in English or Japanese. To support this, we organize and run the following events. What Shall We Do? What shall we do to help improve people's language proficiency? Everyone has different starting points. What needs to be done to learn languages? Focus on mastering basic vocabulary and grammar, such as junior high school-level English. Read (also read aloud) Write Listen Practice shadowing to improve your pronunciation and intonation Speak with others as much as possible I think you can do steps 1 through 5 on your own. However, number 6 requires you to take the initiative and create opportunities on your own, unless you're willing to spend money on language schools or online conversation lessons. Probably what many of our members are lacking is opportunities to speak the language. Since we could add steps 1 to 5 later if needed, we decided to start by providing speaking opportunities first. Japanese Cafe It is a casual meeting to enjoy conversations held in lunch time, fortnightly, also known as the pizza party. It's been nine months since we've started in April 2022. Since the event is conducted entirely in Japanese, it mainly attracts participants with intermediate to advanced Japanese proficiency. Having fun is key, and our goal is to help people communicate more smoothly in Japanese. Japanese cafe Japanese Study Sessions While providing opportunities to speak is essential, the Japanese Cafe alone cannot meet everyone's diverse needs. That's why we launched the Japanese study sessions in August 2022: 30-minute weekly sessions held in small groups. Beginner class - Learn greetings, Hiragana, Katakana, useful phrases for everyday life. Conversation Class (Beginner to Intermediate) - Practice role-playing situations like shopping or making phone calls. Use newly learned phrases in conversation and expand vocabulary with related words. Kanji class (Beginner to Intermediate) - Learn Kanji while practicing new phrases. Business Class (Intermediate to Advanced) - Learn business Japanese vocabulary and expressions using real examples from Slack communications. English Cafe It started in July 2022. Each session lasts 30 to 60 minutes and is held weekly in the late afternoon. The event is held at the Jimbocho office, but participants also join from outside the Global Development Team, including some who attend remotely from the Muromachi office. (Unfortunately, no pizza is provided.) So far, we've organized various activities such as English games, presentation practice, and group discussions. Similar to the Japanese Cafe, most participants are at an intermediate level or higher and are looking to practice conversation. However, since the sessions are typically held in small groups, we can adjust the level based on the participants each time. We also welcome beginner-level participants and those who want to discuss their concerns about learning English. English cafe (It's packed. People gathered for the photo shooting.) Individual English Sessions These 30-minute weekly sessions began in April 2022, designed for those who aren’t very familiar with English. For the first six months, the sessions were held twice a week. As participants grew more comfortable speaking English, the frequency was reduced to once a week.Participants have become noticeably more proactive in speaking English compared to when the sessions first began. They said, "I feel more comfortable speaking English," and "I have more confidence now." Having something to say is a key factor in learning a foreign language. I hope they will continue improving their skills. Reflections and Future Plans We started with the goal of enhancing communication among team members. Both the Japanese and English sessions have a friendly, relaxed atmosphere and have been very effective for team building. Even within the same group, some people rarely have the chance to talk to those they don’t work with directly. By attending the Japanese cafe or the English cafe, you can communicate with people you usually don’t have a chance to talk to. Participants come from diverse backgrounds, and some are native speakers of neither Japanese nor English. Through these conversations, you get the chance to explore a variety of topics and gain insights into different cultures. The Language Skill Up Project is still in its infancy and we are exploring more effective ways. I hope those who haven’t joined yet will give it a try. We also welcome your ideas and suggestions. I hope this can be a place that helps people stay motivated and inspires them to create more opportunities to speak outside of the sessions as well. There’s no magic potion to improve your foreign language skills. It all comes down to your own effort. In other words, you will be able to speak the language as long as you keep on doing it without giving up. If you don't try to create opportunities to speak the language after reaching a certain level, unfortunately it is easy to get rusty. There will always be something new to learn, so continuous study is essential. Once studying becomes a habit, it turns into a natural part of your life rather than just a task.
アバター
Hello Hello! My name is Nakagawara from the KINTO ONE Development Group. I work as a front-end engineer on a project, using Next.js and TypeScript for development. This time, I will talk about code reviews, which are essential for team-based development. I would like to introduce the points I pay attention to when performing code reviews, both as a reviewee or a reviewer. When Requesting a Code Review as a Reviewee Let's get straight to the point. First, I would like to share two things I pay attention to when creating a pull request (I'll call them PR from now) and requesting a code review. 1. Write clear commit messages I believe that by making an effort to write concise and clear commit messages, the granularity of commits will naturally improve. If necessary, I include supplemental explanations or reference links from the second line onward. I also add emoji prefixes to my commit messages. It makes it visually easier to understand the purpose of each commit , and since the emojis carry meaning, they naturally help discourage cramming too many changes into a single commit 🌈 Below is a commit template that the FE team actually uses. # ==== Format ==== # :emoji: PBI_id Subject # # Commit body... # ==== Emojis ==== # 🐛 :bug: Bug fixes # 👍 :+1: Functionality improvement # ✨ :sparkles: Partial feature addition # 🎉 :tada: A major feature addition worth celebrating # 🎨 :art: Visual additions and tweaks # 🔧 :wrench: Feature fix # ♻ :recycle: Refactoring # 🚿 :shower: Clean-up of deprecated or unused features # 📝 :pencil: Documentation and comment updates # 🚚 :truck: File relocation # 👕 :shirt: Lint fixes and style adjustments # 🤖 :robot: Test additions and fixes # 🚀 :rocket: Performance improvements # 🆙 :up: Updating dependencies and related packages # 👮 :cop: Security improvements and resolving warnings When creating the template, the following articles served as reference: atom Getting Fun and Beautiful Commits Using Emojis And here is an actual commit👇 I consciously make sure the purpose of the commit is clear when viewed in isolation. 2. Using a template to enrich PRs The FE team has a template available for PRs. Since it clarifies the perspectives from which the reviewer should check during a review, it is expected to improve review efficiency. Below is a template we actually use. Since we use JIRA for the ticket management of tasks, we include a link to the ticket , specify what this PR will and will not address , and outline what will become possible . ## Link to ticket * https://example.com ## What was done * What was accomplished in this pull request? ## What was not done * What is explicitly not included in this pull request? (If nothing, write "None". If excluded, indicate when it will be addressed.) ## What this enables * What new functionality or behavior does this pull request enable? (If none, write "None"). ## What this disables * What functionality or behavior is no longer possible due to this pull request? (If none, write "None"). ## Other notes * Additional context for reviewers (e.g., implementation concerns, areas to pay special attention to, etc.) When creating the template, the following articles served as reference: Let’s Create a Pull Request Template to Review Efficiently! In addition to the above, I consciously add information as needed. For example, in the case of a PR addressing a bug, I also include the cause , and if there is a UI change, I attach screenshots or videos showing the states before and after the fix . This is because I believe this will facilitate a smoother understanding of the intent of the PR and the code. When Reviewing Code as a Reviewer Next, I'd like to talk about three points I pay attention to when reviewing my team members' PRs. 1. Understanding the overview of a PR In the previous section, I mentioned using a template to write a PR carefully. So first, I make sure to read the overview carefully. I believe that understanding the background, intent, and what is not being addressed here , and then reviewing the code improves review efficiency and helps avoid off-target comments. 2. Check locally The code can be checked from a PR change file. However, for the following reasons, when there is a fix that affects behavior, I always pull the branch into my local environment to check it. To check the behavior and appearance To review the entire code To check the behavior and appearance By running the code myself, I can check whether the expected behavior and appearance have been achieved, as well as understand the purpose and intent of the change. If you only look at the code without understanding why the change was made, you may not be able to perform an appropriate review, and there is a risk of regression when you make future changes to that part. So, I try to follow the processing of the code with my own eyes as much as possible to better understand it. By doing this, I can catch overlooked issues or unintended side effects caused by fixing such issues, which helps prevent unexpected bugs from occurring .🐛 To review the entire code In addition to understanding the processing, you may also notice areas that could be optimized, such as "this and this perform similar processing, so it seems like they could be combined." 3. Labeling review comments I often add labels such as [imo] or [nits] at the beginning of comments to clarify their tone or nature. (though I still tend to forget this at times, so I want to make it a habit). Conclusion I've written a lot, but to sum it up: when doing code reviews, I try to stay mindful of what would make the review process easier, both from the reviewer’s and the reviewee’s perspective. My future challenges include not just being thorough but also improving the speed of my reviews and developing my own consistent set of review criteria to avoid variations in quality. It might also be valuable to align on these review perspectives as a team. I hope this article offered you some helpful insights into code review. Thank you for reading to the end!
アバター
0. はじめに Androidアプリ「 KINTO かんたん申し込み 」の開発を担当している Choi Garamoi です。 このアプリでは KMP を導入し、一部のビジネスロジックを iOSアプリ と共有しています。 今回はよりスムーズなKMPプロジェクtの開発のため Tuist を試した結果をまとめました。 1. 概要 Android Studioでは、KMP Applicationプロジェクトは新規プロジェクトウィザードを使って作成します。作成後は以下のような Monorepo になります: KmpProject/ ├── androidApp/ # Android専用モジュール(Kotlin/Android) ├── iosApp/ # iOS専用モジュール(Swift) └── shared/ # 共通ビジネスロジック(Kotlin/Multiplatform) Androidの Gradle と同様に、iOSでもモジュール化とチーム開発に適したビルド環境が重要です。 本記事では、それを Tuist を使って構築する方法を紹介します。 2. Xcodeプロジェクトの問題 Xcodeはアプリのビルドに必要な情報を *.xcodeproj で管理していますが、いくつかの課題があります。 マージコンフリクトが頻発する : Xcodeは設定を変更すると、 project.pbxproj ファイルを自動的に更新します。このファイルは構造化されていないテキスト形式のため、複数人が同時に編集するとGit上でコンフリクトが頻繁に発生します。 実質的に意味のない差分が生成される : XcodeのGUI操作によって、実質的な変更がなくても多くの差分が生じ、履歴が煩雑になります。 自動化が困難 : 多くの設定がGUI依存であるため、CI/CDやスクリプトによるビルド自動化が困難です。 レビューし難い : project.pbxproj は可読性が低く、変更内容のレビューがしづらくなります。 拡張性に限界がある : チーム規模が大きくなると、複数ターゲットやビルド設定の管理が煩雑になります。 *.xcodeproj ディレクトリは、Androidプロジェクトで言うところの Gradle と .idea ディレクトリ を合わせたようなもので、ローカルのXcode設定とiOSアプリのビルド設定を分離できません。 Googleトレンド でも「xcode conflict」の検索数が「xcode dev」に比べて多く、開発時の衝突が多いことがうかがえます。 3. Tuist とは Swift 言語で XcodeのProjectsとworkspaces を生成と Xcode のターミナルツールを組み合わせてビルドも出来るツールです。 主な機能は モジュール化サポート 環境独立的ビルド(チーム開発指向) 自動化サポート Swift Package Manager サポート です。 他のXcodeのビルドツールとしては Swift Package Manager 、 XcodeGen 、 Bazel があります。 Swift Package Manager : Gradleの依存性管理機能( dependencies ブロック)だけを提供します。 XcodeGen : ツールの設定値チェックが足りないため人的ミスが発生しやすいです。 Bazel : 大規模プロジェクトを対象にするため使い方が複雑で、中小規模のプロジェクトにはオーバースペックです。 4. Tuist を導入する 以下は Migrate an Xcode project を基本に、最新情報を反映した手順です。 4-1. Tuistをインストールする brew update brew upgrade tuist brew install tuist Homebrew 以外のインストール方法は マニュアル をご参照ください。 4-2. Tuist設定ファイルを追加する Tuist.swift 、 Project.swift 、 Tuist/Package.swift の3つのファイル(Manifestファイル)を追加します。 KmpWithSwift/ ├── Tuist.swift ├── Tuist/ │ └── Package.swift ├── Project.swift ├── androidApp/ │ └── ... ├── iosApp/ │ └── ... └── shared/ └── ... 4-3. 設定にモジュール( Target )を追加する Project.swift が主な設定ファイルで、他のファイルは Migrate an Xcode project のサンプルをそのまま利用しても問題ありません。 4-3-1. Tuist.swift import ProjectDescription let tuist = Tuist(project: .tuist()) 4-3-2. Project.swift ターゲットの設定とKMP共通モジュールのビルドスクリプトを追加する。 infoPlist : 全体画面の設定。 scripts : KMP共通モジュールをビルドするコマンド。 import ProjectDescription let project = Project( name: "KmpWithSwift", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "ktc.garamoi.choi.kmp.with.tuist.App", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["iosApp/iosApp/**"], resources: ["iosApp/iosApp/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ] ) ] ) 4-3-3. Tuist/Package.swift // swift-tools-version: 6.0 import PackageDescription #if TUIST import struct ProjectDescription.PackageSettings let packageSettings = PackageSettings( productTypes: [:] ) #endif let package = Package( name: "App", dependencies: [ ] ) 4-4. 古いXcodeプロジェクトを削除する ./iosApp/iosApp.xcodeproj を削除します。 ./.gitignore に *.xcodeproj を追加します。 4-5. 確認 Tuistの設定が正しくできたか確認します。 TuistのManifestファイルをXcodeで開けることを確認します。 # KmpWithSwiftディレクトリで tuist edit TuistでXcodeプロジェクトを生成します。 # KmpWithSwiftディレクトリで tuist generate Xcodeが開いたらアプリを実行します。 アプリが正常に起動すれば完了です。 5. 共通機能からiOS設定を分離する 共通モジュールの ./shared/build.gradle.kts は、共通のビジネスロジックのビルド設定とiOS専用の XCFramework のビルドの責任範囲が適切に分離されていません。 kotlin { // ... listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { it.binaries.framework { baseName = "shared" isStatic = true } } // ... } 5-1. 対応 下記のiOSの XCFramework 設定を :shared から分離して ios へ移動すると、より自然な形で設定でき、マルチモジュール化も楽になります。 it.binaries.framework { baseName = "shared" isStatic = true } 5-2. 手順 ./iosApp/shared モジュールからXCFrameworkをビルドする。 App ターゲット( ./iosApp/iosApp ディレクトリー)のスクリプトを更新する。 5-2-1. :iosApp:shared モジュール追加 ./iosApp/shared/build.gradle.kts ファイルを追加し、 ./settings.gradle.kts にモジュールを追加します。 Androidの設定は不要です。 // ./iosApp/shared/build.gradle.kts plugins { alias(libs.plugins.kotlin.multiplatform) } kotlin { listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { it.binaries.framework { baseName = "shared" isStatic = true export(projects.shared) } } sourceSets { commonMain.dependencies { api(projects.shared) } } } // ./settings.gradle.kts // ... 省略 ... rootProject.name = "KmpProject" include( ":androidApp", ":iosApp:shared", ":shared" ) ./shared/build.gradle.kts からXCFramework設定を削除します。 // ./shared/build.gradle.kts plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) } kotlin { // ... 省略 ... iosX64() iosArm64() iosSimulatorArm64() // ... 省略 ... } // ... 省略 ... 5-2-2. Tuistターゲット更新 Project.swift の scripts のGradleコマンドを更新します。 scripts のモジュールを :shared から追加した :iosApp:shared へ変更します( ./gradlew :shared:embedAndSignAppleFrameworkForXcode ➡️ ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode )。 // ./Project.swift import ProjectDescription let project = Project( name: "KmpWithSwift", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "ktc.garamoi.choi.kmp.with.tuist.App", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["iosApp/iosApp/**"], resources: ["iosApp/iosApp/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ] ) ] ) 6. マルチモジュール化 アプリが成長するとフィーチャーモジュール化が必要になります。 %%{ init: { 'theme': 'neutral' } }%% graph TB App ==> FeatureA App ==> FeatureB FeatureA --> :iosApp:shared FeatureB --> :iosApp:shared しかし各フィーチャーが :iosApp:shared が必要な場合、Tuist設定は下記のようになります。 // ./Project.swift import ProjectDescription let project = Project( name: "KmpWithSwift", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "ktc.garamoi.choi.kmp.with.tuist.App", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["iosApp/iosApp/**"], resources: ["iosApp/iosApp/**"], dependencies: [ .target("FeatureA"), .target("FeatureB") ] ), .target( name: "FeatureA", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.FeatureA", infoPlist: .default, sources: ["iosApp/FeatureA/**"], resources: ["iosApp/FeatureA/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ] ), .target( name: "FeatureB", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.FeatureB", infoPlist: .default, sources: ["iosApp/FeatureB/**"], resources: ["iosApp/FeatureB/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ] ) ] ) この設定では下記の2つの大きな課題があります。 共通のXCFrameworkがフィーチャーモジュール( :iosApp:shared )の数の分ビルドされてしまいます。 フィーチャーモジュールのターゲット設定やビルドオプションによっては、アプリが複数バージョンの :iosApp:shared を利用してしまう恐れが有ります。 この問題を解決するために、 :iosApp:shared をTuistターゲットでラップします。 6-1. Wrappingターゲット追加 フィーチャーモジュールが直接にGradleの :shared モジュールを使わず、Xcodeのターゲットを共有するように KmpCore ターゲットを追加します。 %%{ init: { 'theme': 'neutral' } }%% graph TB App ==> FeatureA App ==> FeatureB FeatureA ==> KmpCore FeatureB ==> KmpCore KmpCore --> :iosApp:shared ソースコードは iosApp/shared/** で :iosApp:shared と同じですが、KMPで生成するネームスペースとカプセル化してWrappingターゲットを使うように KmpCore にします。 この対応により、KMP共通コードの情報を持っているターゲットは KmpCore のみになります。 // ./Project.swift import ProjectDescription let project = Project( name: "KmpWithSwift", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "ktc.garamoi.choi.kmp.with.tuist.App", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["iosApp/iosApp/**"], resources: ["iosApp/iosApp/**"], dependencies: [ .target("FeatureA"), .target("FeatureB") ] ), .target( name: "FeatureA", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.FeatureA", infoPlist: .default, sources: ["iosApp/FeatureA/**"], resources: ["iosApp/FeatureA/**"], dependencies: [.target(name: "KmpCore")] ), .target( name: "FeatureB", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.FeatureB", infoPlist: .default, sources: ["iosApp/FeatureB/**"], resources: ["iosApp/FeatureB/**"], dependencies: [.target(name: "KmpCore")] ), .target( name: "KmpCore", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.KmpCore", infoPlist: .default, sources: ["iosApp/shared/**"], resources: ["iosApp/shared/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ] ) ] ) 6-2. KmpCore で shared を露出する 単純に KmpCore ターゲットが shared へ依存性を持つだけでは FeatureA と FeatureB から :shared のコードへアクセスができません。 FeatureA と FeatureB から KmpCore を経由してKMP共通コード(Gradleの :shared モジュール)へアクセスできるように追加設定が必要です。 まずは KmpCore ターゲットに settings 設定を追加します。 // ./Project.swift import ProjectDescription let project = Project( name: "KmpWithSwift", targets: [ // ... 省略 ... .target( name: "KmpCore", destinations: .iOS, product: .framework, bundleId: "ktc.garamoi.choi.kmp.with.tuist.KmpCore", infoPlist: .default, sources: ["iosApp/shared/**"], resources: ["iosApp/shared/**"], scripts: [ .pre( script: """ cd "$SRCROOT" ./gradlew :iosApp:shared:embedAndSignAppleFrameworkForXcode """, name: "Build KMP" ) ], settings: .settings(base: [ "FRAMEWORK_SEARCH_PATHS": "iosApp/shared/build/xcode-frameworks/**", "OTHER_LDFLAGS": "-framework shared" ]) ) ] ) KmpCore ターゲットが shared ネームスペースを KmpCore ネームスペースで露出するするように下記のSwiftファイルを追加します。 // ./iosApp/shared/KmpCore.swift @_exported import shared 6-3. 確認 Xcodeでプロジェクトをビルドしたら FeatureA と FeatureB が import KmpCore したら :shared へアクセス出来ます。 例えば :shared モジュールに SomeModel ( shared/src/commonMain/kotlin/ktc/garamoi/choi/kmp/with/tuist/SomeModel.kt )のクラスがある場合 FeatureA から下記のようにアクセスできます。 import Foundation import KmpCore public class SomeFeatureAClass { let model: SomeModel // ... } もしコンパイルエラーが発生する場合は、ビルド順やキャッシュの影響で一度目のビルドが不安定になることがあります。その場合はクリーンビルド又は複数回のビルドを試すことで解決できます。 7. 結論 Xcodeの *.xcodeproj は自動化とチーム開発に適していません。 Xcodeプロジェクトの *.xcodeproj の代替として Tuist の使用を推奨します。 KMP共通モジュールのXCFrameworkの生成をXcodeプロジェクトのターゲットでラップすることで、フィーチャーモジュール化が容易になります。 8. 参考 Kotlin Multiplatform : Kotlin言語でクロスプラットフォーム開発ができる色んなツールを提供するKotlin公式プロジェクト。 Gradle : Android、Javaプロジェクトのde-factoビルドツール。 Tuist : Xcodeプロジェクトのビルドツール。 Swift : AppleがObjective-Cの代わりに開発したOOP言語。 Xcode : Appleプラットフォーム用のIDE。 Xcode / Projects and workspaces Swift Package Manager : Swift 言語の公式依存性管理ツール。 XcodeGen : YAMLとJSONで Xcode Project を生成するツール。 Bazel : Googleが開発したビルドツール。大規模 Monorepo を対象にする。 Monorepo Explained : 複数のSWを1つのレポジトリーで管理する仕組み。 Google Trends : xcode conflict , xcode merge , xcode dev : Xcodeの開発全般的な検索に比べてXcodeのコンフリクトの検索の割合が高い。 What is project.pbxproj in Xcode Project configuration / Projects / Project settings : Android Studio, IntelliJ IDEAの .idea ディレクトリーの説明。 Migrate an Xcode project : マニュアルで既存のXcodeプロジェクトをTuistプロジェクト化する手順。 Homebrew : macOSのシステムパッケージ管理ツール。 Install Tuist Xcode / Bundles and frameworks / Creating a static framework Swift logo : 下段から公式ロゴをダウンロード出来ます。 Kotlin logo Gradle logo Tuist logo KINTO かんたん申し込み : Androidアプリ KINTO かんたん申し込み : iOSアプリ Choi Garamoi
アバター
Introduction My name is Endo, and I am the leader of the QA group at KINTO Technologies. In this article, I'd like to give you a look into our daily QA work especially through our daily efforts on subjects that anyone involved in QA may have encountered in the course of their daily tasks. There are already a couple of articles about QA work: Manager Zume's Quality Assurance Group Introduction Team Member Okapi's Increased Awareness of QA So I hope you will read them as well. I hope this article sparks conversations like, 'Here’s how we do it at our workplace,' or 'How do you usually handle this kind of task? I'd love to hear your thoughts! QA Workflow and What We Focus On Our QA work mainly involves: QA for development projects QA for maintenance and updates Test Automation Internal QA improvements QA work improvement activities Study sessions   In this article, I'll walk you through the QA workflow for projects and share the key perspectives we keep in mind. Different Views on QA The outline of QA work is explained during the orientation when joining the company and at the kickoff of each project. However, since all project members are experienced professionals, they have their own ideas and expectations of QA based on their past experience. Because of that, we sometimes run into misunderstandings like, "Isn't QA supposed to check things down to this level?" Such gaps in understanding can even pop up among team members. However, despite differences in what people expect from QA, I think we're mostly aligned on one key point:  "We want to identify and resolve any lingering quality concerns during the QA phase." I think the key is to figuring out how to address those concerns, ease that uncertainty, and make sure the release goes smoothly. To start, we go over the following three main points during the project kickoff, so everyone involved has a clear understanding of QA. (1) Let's build quality together! (2) If you have any concerns about quality, don't hesitate to reach out. (3) QA's feedback isn't absolute, so let's consider whether it can be addressed as a project. Quality is something we build together! Point (1) might go without saying, but quality can't be improved by QA alone. As a result of the aforementioned misunderstandings, we sometimes get requests that lean more toward white-box testing, like: Checking the details at the unit level Checking with a focus on source and data flow And more. QA work mainly focuses on black-box testing, so when it comes to white-box testing, we usually explain that it's more suited to be performed by the development side. These requests often come from past experiences where QA helped before, or from a hope that QA will "definitely" catch issues they might have missed. While there's generally a shared understanding that QA handles things like system tests, acceptance tests, and checks at each phase, people's expectations can still vary depending on their past experience. Some may expect QA to dig into system-level checks like source and data flow, while others expect a more user-centered checks. In general, though, I think QA is widely seen as a team that supports and checks quality control using its own indicators, including the standardization of all development processes. While we in QA want to meet all those expectations, there are limits to what we can catch, and the testing time is not endless. So, it is necessary for everyone to approach quality as something we build "together," especially when working under tight timelines to achieve our quality goals. However, it's not just about QA verifying requirements, pointing out issues, and having them fixed as a routine task. We believe better results come when we're aligned on QA specifications, test plans, and test timing by matching requirements together , aligning test perspectives together , and considering improvements together . And through those thorough discussions, we can apply those insights to test design. Developer Concerns Are a Goldmine of Tips for Improving Quality In reviews from a testing perspective, we can usually identify specific concerns that the development team has. But sometimes, the feedback is more vague, like simply feeling risky about a certain process. Even if those concerns aren't clearly articulated, they're incredibly valuable for QA when defining test perspectives and designing tests. That vague sense of unease often comes from things like complex code, unclear requirements, or uncertainty about whether all the finer details were fully nailed down. Even when everything seems to be working fine, so it's hard to put into words, but developers still have that gut feeling that something might be off. Surprisingly, testing these areas can sometimes reveal unexpected issues, so these instincts shouldn't be taken lightly. As QA, we review the requirements and plan and design tests to address specific concerns. These instincts offer valuable hints that help us strengthen our testing and apply deeper coverage than originally planned. In the QA phase, our goal isn't really to catch unit- or integration-level bugs. Instead, it's about executing hundreds of test cases based on requirements and catching that one critical or fatal bug. That's where QA really shows its value. So, rather than worrying about whether a developer's gut feeling might lead to wasted effort, we try to create an environment where those instincts and hints can be freely shared. By encouraging open conversation, we can uncover areas to strengthen beyond the test perspectives which helps us design better tests and ultimately improve quality. For example, instead of ending a test perspective meeting like, "Here's how we plan to proceed. Please let us know if anything comes up." We try to add a simple question like, "Is there anything in this spec that makes you feel uneasy?" By adding this, the conversation can turn into something like, "Now that you mention it..." Even if you have concerns but can't put them into words, you may feel there's nothing worth sharing. But let's talk to QA anyway! By taking the aforementioned together approach, we can strengthen our test coverage and often uncover a goldmine of valuable insights in the process. Don't Get Distracted by QA Feedback, Stay Focused on the Original Goal If QA is seen as only working within a limited test scope, it can sometimes lead to misunderstandings such as thinking QA merely follows the requirements without understanding the code, focuses on overly detailed issues, and causes delays to the release schedule. However, by providing the above-mentioned explanation in advance, people start to see QA in a completely different light, seeing it as a team that builds quality "together." On the other hand, with the change in perspective, some people start to worry that everything QA "points out" must be addressed before the release can go live. What QA "points out" with testing fall into the following three categories:   Bugs: QA points out behavior (display) that differs from the specifications as understood by QA Improvement requests: There is no problem with the specifications, but QA suggests a change to improve the functionality Questions: Ask about unclear points in the specifications For bugs, QA points them out when we notice behavior that doesn't match the expected specifications. The development team reviews the issue, makes any necessary fixes, and then QA rechecks the fix. Improvement requests are suggestions where there is no issue with the specifications, but it might be better to improve them. For example, if most screens have a button in the top right, but one screen has it in the top left, there's no problem with the button's functionality, but QA might propose moving it for consistency. Questions arise when QA finds unclear parts of the specifications while testing. We ask for clarification first, before labeling anything as a bug. It doesn't mean that all of these points need to be addressed before release. Ideally, every issue should be addressed, and every question answered, but depending on the project's circumstances, it is not realistic to tackle everything before release. If it's difficult to address all of the issues, it's the project, not QA, that decides which ones to tackle before release, based on their importance and priority. QA's work is to conduct testing based on the idea of how things should be ideally according to the requirements. Naturally, even if the requirements are well-defined, there may be some parts of the specifications that are unclear. Ideally, everything would be addressed, but within the limited time available, it is important not to lose sight of the original goal by checking and organizing the final specifications through QA's feedback. This leads to point (3), as a project, it's crucial to align on what kind of service will be delivered, and when. With that shared understanding, we can work toward building quality to meet that goal. This is where it's important for QA to provide solid support in terms of quality. Now, this might sound like we're suggesting that, given the time constraints, it's okay to just meet the bare minimum requirements and compromise on quality to release the service, but that's not the case at all. As mentioned earlier, QA's work is to help the team move toward the project's defined goals by verifying whether the specified requirements are fully met. At the same time, if there are issues that could negatively impact the user experience, we will persistently discuss issues with stakeholders to address them. It's not about making compromises, nor is it about QA forcing our feedback onto the team. Instead, we focus on the project goals and the end users receiving the service, and we work together to decide how the project should respond and take action accordingly. If a critical issue were to occur after release, the impact could be significant. So, QA stays fully engaged to help ensure quality is upheld. However, in larger projects, potential concerns are typically addressed early through the interviews mentioned in point (2). And given the nature of the development process, it's rare for a critical issue to suddenly appear at the very end. As a result, it’s extremely uncommon for the release date to be delayed due to QA work. Ultimately, what matters most is working together to achieve the target quality by the target release date. So, to repeat the important point: QA doesn't just point out issues and ask for fixes. It's about staying focused on the project's original goal to build quality together . For example, delivering the intended service to customers within the planned timeframe while making sure users aren't affected by any inconvenience or negative impact. Conclusion In this article, I focused on our mindset as QA and the communication practices we value, but from a more technical perspective, QA is also the only team that can take a cross-functional, bird's-eye view across all projects and products. If the opportunity arises, I'd like to share how QA provides support along with our approaches to test planning, defining test perspectives, and test design. Sometimes, people approach us with QA requests a bit hesitantly. But the point is that we're in this together . We want to build a relationship where everyone feels comfortable collaborating without hesitation. After release, we often hear, "It was a huge help!" "Thanks so much!" At those times I always say, "Not at all! Thank you for taking QA's feedback seriously and responding with such care."I'm deeply grateful to everyone involved in the project and have great respect for their commitment. We strive every day to build trust by fully supporting the quality of the services we deliver. And when a service we've built together ends up being truly useful to users, I believe that's the true reward of working in QA. I hope this article has sparked some interest in working in QA. Also, if you're working in QA, I'd love to hear your thoughts and exchange ideas. Feel free to share your feedback on Twitter! https://twitter.com/KintoTech_Dev/status/1619979941856280577?s=20
アバター
はじめに こんにちは!セキュリティ・プライバシーG所属の たなちゅー です。 本記事では、弊社で最近発生した生成AIチャットツールに関連するセキュリティ事案についてお話しします。技術的に特別新しいものではありませんが、発生した状況が少し珍しいケースだったため、紹介させていただきます。 事案の概要 弊社では、生成AIチャットツールを全社員が気軽に利用できる環境を整えています。ある日、そのツールを使用していた社員が生成AIへ質問した際に、回答として提示されたリンクへアクセスしたところ、「サポート詐欺サイト」が表示される事案が発生しました。 生成AIチャット イメージ図 サポート詐欺サイト イメージ図 セキュリティ製品でもブロックできず結果としてサポート詐欺サイトへ誘導されましたが、社員の冷静な判断により、大きな被害を防ぐことができました。 このことから、ブラウザによるインターネット検索と同様ですが、生成AIが提示するリンクが常に安全であるとは限らないことを実感する事案となりました。 事案の発生要因 生成AIがサポート詐欺サイトを提示した要因調査を目的に、インターネットアーカイブサービス「 WAYBACK MACHINE 」で生成AIが提示したWebサイトを検索したところ、このWebサイトが過去に正規と思われるコンテンツを提供していることが確認されました。 また、このWebサイトを参考情報として紹介しているサイトが数件、存在することも確認されました。 このことから、問題となったWebサイトの過去のコンテンツ情報や紹介しているWebサイトの情報をもとに生成AIが学習した結果、生成AIが誤った情報を提示するハルシネーションに類する現象が発生し、サポート詐欺サイトのリンクを提示した可能性が考えられます。 以下は調査結果をもとに問題となったWebサイトのコンテンツ変遷を整理した内容です。 2012年~2018年前半 ドメイン名に合致したコンテンツ履歴あり。この時点では信頼性のあるサイトと判断されていたと推測される。 2018年後半~2019年前半 ドメイン管理サービスの販売画面が表示され、運営者がドメインを手放した可能性がある。 2019年後半以降 ドメイン名に関連性のないコンテンツ(病気、オンラインカジノ、偽警告画面、ドメインパーキングなど)の履歴あり。 セキュリティ製品で検知できなかった要因調査については、「 付録:調査メモ 」をご覧ください。 類似事案への対策 以下の観点から、このような事案に対して現時点では根本的な対策を講じることは非常に難しいと考えられます。 生成AIの学習の課題 この事案が発生した背景として、問題となったWebサイトの過去のコンテンツ情報や紹介しているWebサイトの情報をもとに生成AIが学習した可能性があります。一度学習されたWebサイトのコンテンツが変更されても、そのセキュリティリスクが生成AIの回答に反映されることは難しいと考えられます。 最大の対策は「知ること」 この事案から得られる教訓は、「生成AIが提示するリンクが常に安全であるとは限らない」ということを知ることです。事例を学び、実際に遭遇したときにどのように対処すべきかを理解することが大切です。 まとめ 今回の事案は、生成AIが提示するリンクが必ずしも安全ではないことを示す、少し珍しいケースでした。不正サイトの特性によっては、セキュリティ製品でも検知が難しい場合があります。また、生成AIが学習した後にサイトコンテンツが変更されると、そのリスクを反映できない可能性もあります。 現時点では、根本的な対策は難しいと考えられますが、こうした事例を知ることで、生成AIを利用する際のセキュリティ意識を高めるきっかけになればと思います。 付録:調査メモ セキュリティ製品で検知できなかった要因調査のメモです。あくまでも簡単な調査となりますので、参考程度にご覧ください。 1. セキュリティベンダーの検知状況 弊社で利用しているセキュリティ製品や「 VirusTotal 」で問題となったWebサイトのドメインを検索したところ、ほぼ全てのベンダーが「安全」判定となっていました。 2. Webサイトのソースコード 問題となったWebサイトのソースコードを確認したところ、「domaincntrol[.]com(c の後ろに o 無し)」へリクエストを送信後、レスポンス情報をもとに、訪問者の誘導先を動的に決定する仕組みが実装されていると考えられます。 実際に安全な環境で数回アクセスを試みたところ、サポート詐欺サイトやドメインパーキングなど、異なるWebサイトへ遷移することが確認されました。これにより、セキュリティベンダーによる悪性判定を回避した可能性があります。 3. サポート詐欺サイトのホスティング環境 最終的に表示されるサポート詐欺サイトは「web.core.windows[.]net」ドメインにホストされており、Microsoft Azure環境が利用されていると推測されます。Microsoft Azureに限らず、クラウドサービスを利用した不正サイトは、環境構築の容易さや業務影響の観点からクラウドサービスのドメインをブロックすることが事実上不可能であることから、セキュリティ製品でブロックすることが難しいと考えられます。 ※本記事公開時点で今回のサポート詐欺サイトが、Microsoft Azureから削除されていることを確認しています。 4. PublicWWWでの調査結果 Webサイトのソースコードを検索できるツール「 PublicWWW 」を用いて、問題となったWebサイトの特徴的な文字列「domaincntrol[.]com/?orighost=」を検索したところ、2万件以上のサイトでこの文字列を含むコードが使用されていることが確認されました。また、その中からいくつかのサイトを調査した結果、同様にサポート詐欺サイトへ誘導される挙動が確認されました。
アバター
UUIDとは何でしょう? どのバージョンを使用すればよいでしょうか? 最近、データベース内のキーが重複していたためにサービスがダウンするというインシデントに対応しなければなりませんでした。私のチームと私は、これらがUUIDだったため頭を悩ませていました。ご存知のとおり、これらは「一意」の識別子です。どうして重複が生じるのでしょうか?結局、この問題は、同じUUIDが2回生成されたことではなく、サービスが同じイベントを2回追加しようとしたことが原因であることがわかりました。この出来事がきっかけで、UUIDについて考えるようになりました。UUIDとは何でしょうか?UUIDはどのように生成されるのでしょうか?UUIDの使用例は?そして最も重要なのは、どのバージョンを使用すべきかということです。 UUIDとは何でしょう? UUIDは通常、リソースのIDを提供するために使用されます。UUIDは「Universally Unique IDentifier(汎用一意識別子)」の略称です。その名称を見ると、生成される値の一意性が強く期待されているようです。これには十分な理由があります。例えば、数千兆のUUIDなど、膨大な量のUUIDを生成したとしても、それらが一意である確率は99.999%です。これらの確率の背後にある数学に興味がある方は、 この非常に優れた記事 を読むことをお勧めします。 UUIDは「保証された一意性」ではなく「実質的な一意性」です。衝突の可能性は非常に小さいため、ほとんどのアプリケーションでは、UUIDの衝突が発生する可能性よりも、ハードウェアが故障するか、 宇宙線が原因でビットがマシンのメモリ内で反転する 可能性の方が高くなります。 ただし、これらの確率は適切な乱数発生を前提としていることに注意してください。乱数発生器に欠陥があったり予測可能だったりすると、衝突の実際の確率ははるかに高くなる可能性があります。この記事の後半でもう少し詳しく説明します。 ソフトウェアに携わっている方であれば、UUIDがどのようなものかすでにご存知かと思いますが、念のため:UUIDは128ビット幅で、ハイフンで区切られた5つの部分で構成されます。それらは通常、16進数で表され、次のようになります。 ccba8c00-cbed-11ef-ad79-1da827afd7cd 74febad9-d652-4f6b-901a-0246562e13a8 1efcbedf-13bf-61e0-8fb8-fe3899c4f6f1 01943a0e-dd73-72fd-81ad-0af7ce19104b でも待ってください!これらのUUIDは実際には異なるバージョンのUUIDを使用して生成されました。上記のUUIDのリストにおいて、それらはバージョン1、バージョン4、バージョン6、バージョン7の順で使用して生成されます。UUIDにおいてバージョンがどこに示されているかを調べてみてください。 ヒント:中間あたりにあります。 UUIDのバージョンは、UUIDの真ん中にある、UUIDの3番目の部分の最初の文字に示されていることにお気づきだと思います。4番目の部分の最初の文字にもバリアントが示されています。バージョンはUUIDがどのように生成されたかを示すために使用され、バリアントはそのUUIDのレイアウトを示すために使用されますが、おそらくバリアントについて心配する必要はなく、バージョンが最も重要です。 先ほど説明したように、UUID には複数のバージョンがあります。先ほど発見したバージョンインジケーターの他に、各バージョンの違いは何でしょうか?それらはすべて、一意のUUIDを同様に生成できるでしょうか?また、あるバージョンを他のバージョンよりも優先して使用する理由は何でしょうか?もちろん、最新かつ最良のUUIDバージョンを使用すべきですよね?とても良い質問です!UUIDの異なるバージョンを見てみましょう。 バージョン1とバージョン6 バージョン1および6のUUIDは、UUIDを生成したコンピューターの現在の時刻と MACアドレス を使用して生成されます。タイムスタンプ部分は UUIDの先頭にあり、コンピューターのCPUに応じてランダムビットまたは増分カウンターを含む場合があります。MACアドレス部分は最後にあるため、同じコンピューターを使用していれば、その部分が変化することはありません。興味深いことに、MACアドレスはUUIDから取得できるため、UUIDバージョン1または6を生成するとプライバシーのリスクが生じます。しかし、これはこのバージョンのUUIDの利点の1つでもあり、2台のコンピュータが同じUUIDを生成することはありません。そのため、これらのバージョンは、グローバルな一意性が求められる分散システムで役立ちます。 バージョン1と6の違いは、UUIDでタイムスタンプの各部分が使用される順序です。バージョン1とは異なり、バージョン6のUUIDは時系列で並べ替えることができるため、データベース内での順序付けに役立ちます。 バージョン1および6では予測可能な要素(生成時刻とMACアドレス)を使用するため、UUIDを推測可能であり、UUIDを秘密にしておく必要がある用途には適していません。 バージョン2 バージョン2は、タイムスタンプと、UUIDを生成するコンピューターのMACアドレスを使用する点でバージョン1と似ています。ただし、バージョン2では、POSIX UIDまたはGIDという追加の識別子データも使用します。これにより、バージョン2はバージョン1および6よりもランダム性が低くなり、タイムスタンプの使用が少なくなります。その結果、特定の時点で生成できるUUID v2の数は限られており、ほとんどの用途においてあまり望ましくありません。これが使用されることは稀であり、通常、ほとんどのライブラリでサポートされていません。これは、UUID仕様にも記載されていません。 バージョン3と5 バージョン3と5は他のUUIDとはまったく異なります。他のバージョンはランダム性を目指していますが、バージョン3とバージョン5は決定論的であることを目指しています。これはどういう意味でしょう?いずれもハッシュアルゴリズムを使用してUUIDを生成するため、UUIDを再現可能にします。UUIDを生成するためにランダム性やタイムスタンプは使用されず、与えられた入力は常に同じUUIDを生成する必要があります。バージョン3ではMD5ハッシュアルゴリズムを使用し、バージョン5ではSHA1を使用します。 これらのバージョンは、同じ入力データから同じUUIDを繰り返し生成する必要がある場合に特に便利です。例えば、ユーザーの電子メールアドレスに基づいてユーザーのUUIDを作成するとします。異なるサーバーや時間にわたっても、同じ電子メールで常に同じUUIDが生成されるようにしたいとします。もう1つの良い例としては、重複を避けるために何らかのデータに基づいて主キーを生成する必要があるが、データ自体を主キーとして使用するのは良い選択肢ではない場合が挙げられます。 バージョン3とバージョン5のいずれかを選択する場合、SHA1の方が少し安全ですが、計算負荷も大きくなることに留意してください。それがユースケースで懸念事項である場合は、バージョン3を使用してコンピューティングリソースの使用量を削減することをお勧めしますが、ほとんどの場合、より安全なバージョン5を選択すべきです。また、SHA1よりもMD5と衝突が発生する可能性が高くなりますが、その確率は依然として非常に低いです。 バージョン4 バージョン4は、UUIDの最も広く使用されているバージョンです。バージョン4はランダムビットを使用してUUIDを生成するため、UUIDは一意で予測不可能になります。これは乱数発生に大きく依存していますが、すべての乱数発生器が実際に真の乱数を生成できるわけではありません。衝撃的ですよね。 多くのプログラミング言語では、疑似乱数発生器(PRNG)と呼ばれるものを使用しています。ほとんどの場合はこれで問題ありませんが、UUID生成の場合は、システムが暗号論的にセキュアな疑似乱数生成器(CSPRNG)を使用していることを確認する必要があります。 なぜかって?通常のPRNGは、出力を十分に分析すれば予測可能になる場合があります。一方、CSPRNGは、攻撃者が以前に生成されたすべての値を知っている場合でも、その出力を予測することが事実上不可能になるように特別に設計されています。最近のUUIDライブラリのほとんどがデフォルトでCSPRNGを使用していますが、念のため確認してみる価値はあります。 他のバージョンと同様に、予測可能な部分はバージョンインジケーターのみなので、その部分を推測して友達を感心させてみましょう。 これらは、大量のUUIDを生成する必要があり、後で並べ替えたり再現したりする必要がない場合など、ほとんどの用途に最適です。これらは、データベースにおいてキーとしてよく使用されます。 バージョン7 バージョン7は、バージョン4を時系列順に並べ替えられるバージョンとして設計されています。バージョン4と同様に、バージョン7はランダムビットを使用しますが、タイムスタンプを含むため、UUIDはソート(並び替え)可能かつ一意になります。一意性を保ちつつ、作成時間によって並べ替えたい場合に、バージョン7はバージョン4の優れた代替手段となります。 バージョン7でもタイムスタンプにエポックタイムを使用しますが、バージョン1と6では1582年10月15日以降、100ナノ秒間隔の数値を使用しています。これにより、バージョン7での作業が少し簡単になっています。 バージョン8 バージョン8はカスタムなので少し特殊です。ベンダーは希望どおりに実装できます。バージョン8を自分で実装することもできますが、UUIDの3番目の部分にあるUUIDバージョンを尊重する必要があります。おそらくそれを使用する必要はないでしょう。 では、何を使用すればよいでしょうか? ほとんどの人にとって、バージョン4になります。バージョン4は、一意性の保証が最も大きく、比較的安全です(乱数発生器が予測不可能な限り)。UUIDを作成時間によって並べ替えられるようにしたい場合は、MACアドレスの漏洩によるプライバシーの懸念がない限り、バージョン7またはバージョン6を使用できます。場合によってはバージョン3と5が便利ですが、ほとんどのアプリケーションではそれらの使用は制限されます。 データベースキー? データベースキーにUUIDを使用することに関する議論を見たことがあるかもしれませんが、データベースキーにUUIDを使用することを検討している場合は、覚えておくべき事実がいくつかあります。 UUIDは大きく、128ビットを占めます。大量のデータを保存する予定がない場合は、UUID用に追加で占有されるスペースが大きくなる可能性があります。あるいは、32ビットの自動増分整数(オートインクリメント整数)では約2147483647行が得られますが、それでも足りない場合は、64ビットのBIGINTで最大18446744073709551615になります。ほとんどのユースケースの場合、これで十分でしょう。 一部のデータベースでは、キーにUUIDを使用すると、挿入性能が低下する可能性があります。挿入性能が懸念される場合は、自動増分整数の使用を検討するか、少なくともUUIDを使用してデータベースの性能をテストすることをお勧めします。 UUIDは、データの移行を容易にします。自動増分整数を使用すると衝突が発生しますが、UUIDではその問題は発生しないでしょう。 一部のUUIDはソート(並び替え)可能ですが、読みやすくはありません。2つのUUIDに着目すると、どちらが先に来たのかを知るのは非常に困難です。これは非常に些細なことですが、留意すべきです。 ほとんどのデータベースには、UUIDを生成するための何らかのモジュールまたは関数があるため、データベースのドキュメントをチェックしてUUIDの生成方法を確認できます。UUIDを使用する際に性能上の問題や考慮すべき特別な事項がある場合は、おそらくそこで判明するでしょう。 結論 この記事を読む前よりも、UUIDとそれらのさまざまなバージョンについて少し理解が深まったと思います。 バージョン4 UUIDは、ほとんどのアプリケーションで依然として定番です。バージョン4 UUIDは、強力な一意性保証と予測不可能性を備えており、おそらくこれがUUIDに求められるものです。バージョン4 UUIDは主に、データベースキー、分散システム、および調整なしでグローバルに一意な識別子が必要なシナリオで使用されます。 バージョン7は、ランダム性とソート(並び替え)可能性とのバランスが取れているため、時系列的ソートが望ましい場合に適した代替手段です。 バージョン1と6は、グローバルな一意性が必要な分散システムでは役立ちますが、MACアドレスが含まれるためプライバシーに関する懸念を伴います。 バージョン3と5は、特定の入力からUUIDを再現する必要がある場合に便利ですが、MD5はSHA1ほど安全ではないことに注意してください。 自分のシステムでUUIDを使用する予定の場合は、UUIDバージョンを選択する際に次の要素を考慮してください。 一意性に関する自身の要件 時系列的ソートが必要か否か プライバシーに関する懸念 (特に MACアドレスを含むバージョンを使用する場合) ストレージ スペースの制約 (あなたのキーに128ビットは必要ないかもしれません) UUIDの衝突は理論的には起こり得ますが、暗号的に安全な乱数発生器を使用した適切な実装をしている限り、その可能性は非常に低いため、システム設計において大きな懸念事項にはならないはずです。UUIDの衝突が発生した場合(天文学的な確率を覆した場合!)、実際のUUID生成衝突ではなく、重複したイベント処理などのアプリケーションロジック問題が原因である可能性が高くなります。そのような場合は、UUID生成自体に疑問を抱くよりも、アプリケーションの一意性制約の処理を調べることに重点を置いてください。
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の21日目の記事です 🎅🎄 はじめに こんにちは!iOSエンジニアのViacheslavです。 今年、私は当社の Unlimitedアプリ の新機能である 「これなにガイド」 に取り組む機会を得ました。これなにガイドは、車のダッシュボードをスマホでスキャンすることで、車のボタンやスイッチの上に仮想マーカーを表示できる拡張現実(AR)マニュアルです。特定のボタンに対応するマーカーを選択すると、機能を確認できるマニュアルページにアクセスできます。 今日は、この機能の開発中に遭遇した課題の1つである、「画面上でVisionフレームワークによって認識された物理オブジェクトの座標を正確にキャプチャし、それらの座標をARシーン内の3D座標に変換する」という課題に対する、短くてシンプルな解決策を共有したいと思います。 最初は些細な作業に思えたものが、予想以上に複雑であることが判明しました。いくつかのアプローチを模索し、多くの手動計算を実行した後、ようやく、単純かつ驚くほど簡潔な解決策にたどり着きました。ARKit と CoreML の統合に関する情報は比較的少ないので、始めたときに知っていればよかったと思います。では、知識ベースに追加していきましょう! いくつかの前提条件 実際のコードに進む前に、作業する環境を明確に定義してみましょう。 ARSCNView これは、デバイスのカメラからのビデオフィードを表示し、現実世界をキャプチャして3Dオブジェクトを「ブレンド」させてARエクスペリエンスを実現するビューです。 ARSCNView はAppleの ARKit の一部で、ARシーンでの3Dオブジェクトのレンダリングを処理する SceneKit 上に構築されます。 Core ML オブジェクト検出モデル オブジェクトの座標を決定する前に、まずデバイスのカメラによって提供されるビデオ フィード フレーム内でオブジェクトを認識する必要があります。Visionフレームワークは、この目的のためにCore MLオブジェクト検出モデルを利用します。本記事では、読者がすでに使用できるモデルを持っているものと想定します。そうでない場合は、 YOLOv3-Tiny など、ダウンロード可能な事前訓練済みモデルが多数あります。 こちら から入手できます。 最低限のソリューションに必要なのはこれだけです。 ARSCNView からビデオフレームをキャプチャし、Core MLモデルを使用して ARSCNView ビューポート内のオブジェクトの位置を検出し、「ヒットテスト」と呼ばれる手法を適用して3D AR空間におけるオブジェクトの座標を決定します。 ARSCNViewにおいて認識されたオブジェクトの座標をキャプチャする Vision を使用して認識要求を実行する場合の一般的な設定は以下の通りです。 Core MLモデルと VNCoreMLRequest を初期化して、そのモデルを使用して認識を処理します。 let vnModel = try!VNCoreMLModel(for: myCoreMLModel) let vnRequest = VNCoreMLRequest(model: vnModel) { [weak self] request, _ in guard let observations = request.results else { return } // Observations handling } request.imageCropAndScaleOption = .centerCrop 次に、 vnRequest への参照を適切な場所に保存し、次の引数セットで実行できるように準備します。引数のタイプは、ビデオフィードフレームをどこからキャプチャするかによって異なります。 私たちのシナリオでは、 ARSCNView からフレームを渡し、 ARSessionDelegate の session(_:didUpdate:) メソッドにおいてフレームをキャプチャする必要があります。このデリゲートメソッドは、 ARSCNView で表示できる新しいフレームが利用可能になるたびに呼び出されます。 func session(_ session:ARSession, didUpdate frame:ARFrame) { guard let vnRequest else { return } // 1 let options: [VNImageOption:Any] = [.cameraIntrinsics: frame.camera.intrinsics] // 2 let requestHandler = VNImageRequestHandler( cvPixelBuffer: frame.capturedImage, // 3 orientation: .downMirrored, // 4 options: options ) try? requestHandler.perform([vnRequest]) // 5 } コードを分解する: VNCoreMLRequest の参照 :新しいフレームを受信すると、先ほど初期化したリクエストを実行する準備が整います。 カメラ内部パラメータ : frame.camera.intrinsics は、Visionがシーンの幾何学的特性を解釈するのに役立つカメラ較正データを提供します。 画像入力 : VNImageRequestHandler は、ARフレームから取得された生画像データを CVPixelBuffer として受け入れます。 画像の向き : .downMirrored 方向は、Visionのデフォルト方向と比較して、カメラフィードの座標系の反転を考慮します。 リクエストの実行 :準備されたリクエストは、リクエストハンドラーを使用して実行されます。 Vision にフレームを渡し始めると、オブジェクト検出の結果が VNCoreMLRequest 完了ハンドラー内の VNRecognizedObjectObservation オブジェクトの配列として返されます。これらの結果を信頼度レベルでフィルタリングしたり、その他の処理を実行したりすることもできますが、今日は認識された特定のオブジェクトの座標を抽出することに焦点を当てます。 境界ボックス座標の抽出 最初は、 VNRecognizedObjectObservation に boundingBox プロパティ(認識されたオブジェクトを囲む CGRect )があるため、これは簡単に思えるかもしれません。ただし、いくつかの複雑な問題があります。 boundingBox は、物体認識モデルの入力画像に対する正規化された座標系(座標値は0から1の間)であり、それもY軸が反転しています。 カメラフィード、Core MLモデル入力、そして ARSCNView ビューポートのサイズとアスペクト比はそれぞれ異なります。 つまり、 boundingBox を ARSCNView ビューポートの座標系に変換するには、一連の座標変換と再スケーリングの手順が必要になります。これらの変換を手動で行うのは面倒で、間違いが起こりやすくなります。幸いなことに、 CGAffineTransform を使用すると、これを処理できる非常に簡単な方法があります。方法は次のとおりです。 let sceneView:ARSCNView func getDetectionCenter(from observation:VNRecognizedObjectObservation) -> CGRect? { guard let currentFrame = sceneView.session.currentFrame else { return nil } let viewportSize = sceneView.frame.size // 1 let fromCameraImageToViewTransform = currentFrame.displayTransform(for: .portrait, viewportSize: viewportSize) let viewNormalizedBoundingBox = observation.boundingBox.applying(fromCameraImageToViewTransform) // 2 let scaleTransform = CGAffineTransform(scaleX: viewportSize.width, y: viewportSize.height) let viewBoundingBox = viewNormalizedBoundingBox.applying(scaleTransform) return viewBoundingBox } 説明: ビュー座標への変換 : displayTransform(for:viewportSize:) を使用して、検出された境界ボックスは、入力画像の正規化座標系から ARSCNView の正規化座標系に変換されます。 ピクセル寸法へのスケーリング :正規化された境界ボックスは、 ARSCNView ビューポートのサイズに合わせてスケーリングされ、画面のピクセル寸法での境界ボックスが生成されます。 以上です!これで、 ARSCNView ビューポートの座標系に境界ボックスが作成されました。 3番目の座標を取得する 私は、ARシーンの3D座標空間内で認識されたオブジェクトの座標を取得すると約束しました。 そのためには、「ヒットテスト」と呼ばれる手法を利用します。これにより、ビューポート内の任意のポイントで最も近い物理オブジェクトまでの距離を測定できます。この手法は、デバイスからビューポート内の選択したポイントにある物理オブジェクトとの最初の交点まで直線の光線を投影し、その光線の長さを測定するものと考えることができます。この機能は ARKit の一部であり、非常に使いやすいです。 先ほど検出したオブジェクトの知覚可能な中心の3D座標を見つける方法は以下の通りです。 func performHitTestInCenter(of boundingBox:CGRect) -> SCNVector3? { let center = CGPoint(x: boundingBox.midX, y: boundingBox.midY) // 1 return performHitTest(at: center) } func performHitTest(at location:CGPoint) -> SCNVector3? { guard let query = sceneView.raycastQuery( // 2 from: location, allowing: .estimatedPlane, // 3 alignment: .any // 4 ) else { return nil } guard let result = sceneView.session.raycast(query).first else { return nil } // 5 let translation = result.worldTransform.columns.3 // 6 return .init(x: translation.x, y: translation.y, z: translation.z) } 説明: ここでは、ヒットテストを実行するために1つのポイントが必要なため、前の手順から境界ボックスの中心を計算します。 指定された2Dポイントから始まるレイキャストクエリを作成します。 ヒットテストで、ARKitが推定することしかできない非平面の表面や平面を考慮できるようにします。 水平面と垂直面の両方のヒットテストを有効にします(既定値は水平面のみです)。 ARセッションを使用してレイキャストクエリを実行します。交差がない場合は nil を返します。 各 ARRaycastResult には、ワールド空間で検出されたポイントの3D変換を表す4x4行列である worldTransform が含まれます。 columns.3 には、交点の3D位置を指定する並進ベクトルが含まれています。この並進ベクトルは SCNVector3 として返され、ARKit/SceneKitはこれを使用して3D位置を表します。 完了しました!これで、Visionによって検出されたオブジェクトの 3D座標を取得できました。目的に合わせてご利用ください。:) 最後に Unlimitedアプリでは、これらの3D座標を使用して、車内にARマニュアルマーカーを表示します。もちろん、ユーザーエクスペリエンスをよりスムーズにし、マーカーの位置をより安定させるために私たちが採用している追加テクニックは数多くありますが、これはコアテクニックの1つです。 とはいえ、この方法は、考えられる他のあらゆる目的にも使用できます。お役に立てたら幸いです。 最後に、私たちのテストプロセスと、オブジェクト検出後にARマニュアルマーカーがどのように表示されるかについて少し紹介します。 今日はここまでです。読んでいただきありがとうございました! 楽しいクリスマスをお過ごしください。そして、幸せな新年をお迎えください!
アバター
はじめに こんにちは、AIファーストグループのAlexです。 AI技術の急速な発展に伴い、エージェント開発の需要も高まっています。しかし、エージェントに関する最新技術の知見の共有不足や、開発リソースの分散により、効率的なエージェント開発を始めることが難しい状況にあります。そこで私たちは、社内各所で開発したAIエージェントを社内で共有し、技術・ノウハウを集約するためのプラットフォーム「Agent Store v1.0」をリリースしました。 Agent Storeの目的 Agent Storeは以下の2つの主要な目的を持っています: 社内Agent開発の効率向上 既存Agentの再利用により開発サイクルを短縮 テンプレートを活用した迅速な開発を実現 技術・ノウハウの蓄積 社内のAgent関連技術を一元管理 ベストプラクティスの横展開を容易に 利用形態と対象ユーザー Agent Store は、社員が AI エージェントを自由に開発、共有、ダウンロードして再利用するプラットフォームです。 なお再利用に関しては、App Storeのように、ユーザーがエージェントをダウンロードし、自身の環境にデプロイすることを想定しています。 Agent Storeは以下の要素で構成しています。 社内で開発したエージェントを共有するGithubリポジトリ エージェント開発のCI/CDの仕組み Agent StoreのGithubリポジトリ 利用形態 Agent Store v1.0では主にAWS Bedrockエージェントをサポートしています。 エージェントのCI/CDプロセスに関しては、IaC (Infrastructure as Code)を基づいて設計しました。 Agent Storeで共有されたエージェントは、CloudformationでデプロイするためのSAMテンプレートの形でAgent StoreのGithubリポジトリ上で格納されています。 エージェントを再利用したいユーザーは、該当するエージェントのSAMテンプレートをダウンロードし、Cloudformation経由で自分の環境にデプロイする形で利用します。 なお、デプロイに関してはGithub Actionsで自動化しています。 エージェントを新規開発したいユーザーに関しても、Agent Storeが提供している空のSAMテンプレートを利用し、素早くエージェント開発することが可能です。 対象ユーザー(v1.0) AWS Bedrockを利用してエージェント開発を始めたいエンジニア Bedrockで開発したエージェントを社内で共有したいエンジニア 既存のエージェントを探して活用したいエンジニア 想定するユースケース Agent Storeは以下のような利用シーンを想定しています: AIエージェントの新規作成 Agent StoreのCI/CDフローを利用することで開発の高速化を実現 AIエージェントの共有 自作エージェントを社内で共有 共有されたAIエージェントの再利用 既存プロダクトにAIエージェントを搭載 社内業務効率化アプリの開発 一からの開発を避け、開発効率の向上を実現 AIエージェントのPoC 既存エージェントをデプロイして迅速にPoC実施 効果検証の時間短縮 AIエージェント開発ノウハウの取得 類似事例の参照によるベストプラクティスの学習 技術的な障壁の低減 Agent Storeを活用したエージェント開発、共有と再利用のフロー エージェントを新規開発するフロー Agent Storeリポジトリから空のSAMテンプレートを取得し、記入した上でデプロイを行う流れとなります。 SAMについては こちら を参照してください。 なお、エージェントのAction Groupを利用する場合、①Lambda、②エージェントの順番でテンプレート記入・デプロイを行う必要があります。 エージェントを共有するフロー 開発したエージェントのSAMテンプレートを用意し、Agent Storeのリポにプルリクを実行します。エージェントのレビュー担当者にて内容を確認後、問題なければマージを行います。 共有されたエージェントを新たに再利用するフロー 基本の流れはエージェントを新規開発するフローと共通していますが、Agent Storeで共有されているエージェントのSAMテンプレートを取得し、必要に応じて修正・加筆を行った上でデプロイを行う流れになります。 エージェントのアーキテクチャ エージェントのアーキテクチャは以下です。 デプロイしたBedrockエージェントは、必要に応じでAction Groupとして設定されたLambdaを呼び出し実行することができる。 また、スタック管理はCloudFormationで行っている。 また、複数のエージェントが協働するマルチエージェントコラボレーションも構築することが可能です。 今後の展開計画 今後は、Agent Storeの利用促進のために、社内勉強会、ワークショップ、ハッカソンなどを多数企画しています。 また、現在のv1.0は「AWS Bedrockの開発経験があるエンジニア(タイプa)」を対象としていますが、今後は段階的に対象ユーザーを拡大していく予定です: ユーザーの種類 詳細 サポート状況 エンジニア タイプa AWS Bedrockの開発経験があるエンジニア v1.0でサポート中 エンジニア タイプb AWS Bedrock以外のエージェント開発環境を利用したいエンジニア 検討中 非エンジニア、エージェント開発初心者 開発・コーディング経験がない、非エンジニアの社員 検討中 まとめ Agent Store v1.0は、エージェント開発の効率化と知見共有を実現するプラットフォームです。現時点ではAWS Bedrockユーザー向けに機能を提供していますが、今後はより幅広いユーザー層へのサポートを拡大し、様々なエージェントフレームワークにも対応していく予定です。社内のAI開発リソースを最大限に活かし、イノベーションのペースを加速させるため、Agent Storeの機能拡充と進化に積極的に取り組んでいきたいと思います。
アバター
本記事は KINTOテクノロジーズアドベントカレンダー2024 の21日目の記事です🎅🎄 はじめに 皆さん、こんにちは!DLです。KINTOテクノロジーズ(KTC)のグループコアシステム部で、ビジネスディベロップメントチームに所属しています。今は、保険などの付加サービスを備えた自動車リース事業 KINTO ONE の社内システムを導入するため、ラテンアメリカのKINTO事業部と連携しています。ビジネスアナリスト(BA)として私が注力しているのは、 KINTOの業務分析と、ITソリューションの作成でワークフローを合理化して、効率性を向上させることです。 ビジネス分析がグローバルチームの一部となる前は、システム設計がKINTOグローバルビジネスの期待に十分応えられていないことがよくありました。日常業務の複雑さに対応できる、エンタープライズレベルのソリューションが求められていたのです。ここで活躍するのがBAです。日々の業務プロセスを把握し、懸念材料となる課題に対処する上で、BAは欠かせない存在です。ビジネス分析は、グローバルプロジェクトに価値を付加する戦略的な柱と言えるでしょう。 今日は、なにかと誤解されがちなBAの世界へ、皆さんをご案内します。「ただ数字ばかり気にしてる人じゃないの?」とか、「他の人の言うことをメモしてるだけなのでは?」と思うかもしれません。でも、BAの仕事って、それ以上に奥が深いんです。問題解決や戦略、コミュニケーションも欠かせません。 BAの本質は、技術とビジネスの世界を橋渡しすることです。効率アップ、業務の合理化、テクノロジーの導入など、どんな場面でもBAは耳を傾け、分析し、実用的で効果のあるソリューションを形にしていきます。経営幹部からエンドユーザーまで、関係者を巻き込むことでBAは重要な言葉の架け橋となります。そして、ビジネスニーズを効果的なテクニカルソリューションへと落とし込んでいきます。 ストーリー 1 - 聞く力:ビジネスアナリスト(BA)の役割 BAにとって欠かせないスキルのひとつが、聞く力です。ただ言葉を聞くだけでなく、その裏にある本当のニーズに耳を傾けることが大切です。あるプロジェクトで、とある金融会社の非効率的なレポート作成プロセスに取り組んだことがあります。世界中の事業部門から四半期ごとのレポートがメールで届き、その数は1日あたり100通以上。それが障害になっていました。届いたファイルは手作業でダウンロード、統合、エラーチェックされていました。時間がかかり、ミスも起こりやすい状態だったのです。 実際にその現場を見て、関係者と一緒に課題を洗い出していきました。IT部門と連携し、レポートを直接提出できる安全なLANディレクトリ構造を開発しました。バッチ処理やVBAスクリプトの自動化によって、データ確認、コピー、集計の作業がぐっと効率化されました。その結果、手作業の負担を約70%も削減することができ、より付加価値の高い分析に時間を使えるようになりました。この取り組みがうまくいったことで、部門全体でこの方法が採用されることになりました。 ストーリー 2 - 関係者を見落とした教訓 その後、とあるSaaS製品が、BAやユーザーの十分なレビューを経ることなく導入されました。コスト削減やダッシュボード機能が評価された結果の選定でしたが、既存の自動化ワークフローには対応できず、チームは手作業に逆戻りすることに…。 不満の声が上がりましたが、決定は覆らず、ユーザーの間には落胆と不満が残ってしまいました。この出来事からはっきりしたのは、ユーザーの本当のニーズに合ったソリューションを提供するには、初期段階からBAの関与が欠かせないということです。 物語は続きます… ストーリー2は少し残念な展開に見えるかもしれませんが、とても大事な教訓を教えてくれます。それは、プロセスを変えるにせよ、新しい製品をつくるにせよ、 成功のカギはエンドユーザーの声に耳を傾け 、真のニーズを理解することにある、ということです。ここで、KINTOテクノロジーズのグローバルプロジェクトにおいて、BAが果たす重要な役割についてご紹介します。前にも少し触れましたが、グローバルチームは現在、ラテンアメリカのKINTO各部門と連携し、保険などの付加サービスを備えた自動車リース事業 KINTO ONE に取り組んでいます。 グローバルな製品やシステムを開発するということは、国や言語を超えて多様な仕様に対応することです。課題となるのは、各国の異なるニーズに柔軟に対応できるシステムを、どう構築するかという点です。では、どうアプローチすればよいのでしょうか? 各国のビジネスユーザーは、自分たちのプロセスについてはプロですが、そのプロセスは国によって変わります。一方エンジニアは、それぞれの違いを踏まえたうえで、柔軟性のあるシステムを構築するという難題に向き合っています。そこで登場するのが、BAです。BAはビジネスユーザーとエンジニアの間に立ち、両者の懸け橋となるのです。 BAとして私たちは、以下のような体系的なアプローチを取っています。 ビジネスプロセスの収集:各国特有の業務プロセスを丁寧に文書化していきます。 BAとして、このステップは非常に重要です。というのも、BAは基本的にビジネスプロセスの専門家として、すべての工程を詳細まで完全に理解し、どんなステップも見逃さないようにする必要があるからです。このステップを達成するために、KINTOテクノロジーズのBAたちは実際にラテンアメリカの現地企業を訪問し、現場に赴きました。こうした現場の観察は、各プロセスのニュアンスを掴むために欠かせません。 さらに、このステップで重要なのは、企業が現在どんな懸念材料を抱えているのかを理解することです。なぜでしょう?それは、KINTOテクノロジーズが開発するシステムは、こうした懸念材料を解決し、ビジネスユーザーの日常業務に具体的な価値を付加することが求められているからです。この懸念材料にうまく対処できれば、ビジネスユーザーの手作業が減り、生産性と業務効率を上げられます。その効果は、作業時間の短縮、リードタイムの短縮、ユーザーの働きやすさの向上といった形で評価することができます。 ギャップ分析の実施:この分析では、各国におけるプロセスの違いや共通点が浮き彫りになります。 BAとして、このステップでは特に重要なポイントが2つあります。 作業手順の順序:各国で作業手順の順序が似ていれば、それはかなりの朗報です!でも、もし一方の国だけ順序が逆だった場合、両方に対応できるシステムを設計するのは、はるかに複雑になります。たとえば、車両の修理を行う場合:ケース1では、まず修理がKINTOサービスとして承認されて、その後実際の修理作業が始まります。一方ケース2では、先に修理作業が行われます。そしてその後で、そのサービスがKINTOサービスとして含まれているかどうかが確認されます。このように手順に違いがあると、システム設計の難易度が上がるのです。 外部システムとの統合:このステップで欠かせない、もう1つの重要なポイントがあります。それは、現在さまざまな国で使われている外部システムやプラットフォームに目を向けることです。たとえば、ある国では会計システムにSAPを使用していて、別の国では異なるタイプの会計システムを使用しているかもしれません。こういった違いが、グローバル製品に必要な統合を複雑にしているのです。 ソリューションの共同作業:ギャップ分析を使用して、BAはエンジニアとタッグを組みながら、関連のある国すべての要件に対応できる柔軟な設計を進めていきます。 このステップでは、エンジニアの力を借りて複雑なパズルを解いてくような感じです。BAにとってこのステップはかなり重要です。なぜかというと、BAは基本的に両国のビジネスユーザーを代表する立場なので、どちらのニーズにも応えられるシステムを設計する必要があるからです。例え話ですが、ある国のニーズはセダンで、もう一方のニーズはバンだとします。ここでBAはエンジニアと協力して「じゃあSUVを開発しようか?」となるような感じです(笑)。このステップでは、プロセスの一つひとつを細かく検討し、すべての工程が両国のニーズにちゃんと対応できるかを確認していきます。そうして、すべてのステップがうまくつながり、まとまりのある柔軟なシステムとして作成できることを確認します。こういった複雑な課題を解決していくのって、とても楽しいんですよ! 結果として共同努力が生まれ、グローバル製品はビジネスニーズに応えつつ、システムの柔軟性とのバランスもしっかり取れるようになります。グローバルプロジェクトが成功する上で、BAの役割がいかに重要であるかを物語っていますね。 ビジネスアナリスト(BA)の力とは? BA になるとは、どういうことでしょうか?それは、問題を解決する人にも、コミュニケーターにも、戦略家にもなるということです。つまり、疑問を投げかける好奇心、耳を傾ける忍耐力、そして粘り強く答えを探る力を持つということです。最終的には、組織の運営方法、人の働き方、意思決定の方法に、本当の変化をもたらすことがBAの役割です。 BAは目立つ存在ではないかもしれません。でも、私たちの仕事は成果に影響を与え、リソースの無駄を減らし、誰かの仕事をちょっとラクにしているんです。アイデアを現実に変え、ニーズを形に変える。その架け橋となっているのが、BAです。 ひとつ、覚えておいていただきたいことがあります。それは、「聞く力」をあなどってはいけない、ということです。BAでも、ステークホルダーでも、何かを変えたいと尽力している人でも。本当の前進は、聞くこと、言葉の橋渡し、そしてつなぐことから生まれます。 読んでくださって、ありがとうございました!
アバター
前提条件と範囲 本記事では、Android Composeのナビゲーションを、オブジェクト指向の観点から処理する方法について説明します。基盤となる技術をカプセル化し、開発の利便性を高め、特定の技術へのロックインを防止する機能を開発する方法を網羅します。 これにより、Android Composeアプリ向けの高品質なナビゲーションコードが書けるだけでなく、オブジェクト指向開発の考え方も身につけられます。 本記事では、Dagger/Hiltを使用したMVVMアーキテクチャを前提としています。 ここでは、ナビゲーションルート→ViewModel→UIのパスのみを通じてデータを提供し、ナビゲーションルート→UIの直接的なデータ提供は行いません。 Android Navigationバックスタックに関する事前知識が必要となります。 ナビゲーションボタンの素早い連続クリックによるUXの問題などは考慮していません。 ディープリンクには対応していません。 Android OSレベルでサポートされているバックジェスチャの取り扱いについては説明していません。 厳密な引数の検証やパフォーマンスの最適化については触れていません。 本記事で使用している用語や名称は便宜的なものであり、一般的な技術用語や学術的な表現とは異なる場合があります。 この記事は、 KINTOテクノロジーズ・アドベントカレンダー2024 の16日目の記事です🎅🎄 ナビゲーションタイプ 詳細 詳細情報を提供する画面へ移動する際は、一般的にリスト画面から特定の項目画面へ遷移します。例えば、ニュースフィードからニュース詳細、メニューからメニュー項目への移動などが該当します。各ナビゲーションアクションによって、バックスタックが1つ増えます。前の画面に戻るには、バックスタックの一番上( pop )を削除します。 navController.navigate("sub3_1") のように、route stringを指定して NavController.navigate functionを呼び出せます。 @Composable fun NavigationGraph() { val navController = rememberNavController() NavHost(navController, "splash") { composable("main3") { val viewModel: Main3ViewModel = hiltViewModel() Main3Screen( id = viewModel.id, navMain1 = { /* ... */ }, navMain2 = { /* ... */ }, navMain3 = { /* ... */ }, navSub31 = { navController.navigate("sub3_1") }, navSub32 = { navController.navigate("sub3_2") }, navSub33 = { navController.navigate("sub3_3") } ) } } } 切り替え このタイプのナビゲーションでは、ユーザーは別の画面に移動するのではなく、同じ画面内でコンテンツが変化したと認識します。通常、タブや下部のナビゲーションバーで使用されます。 下部ナビゲーションバーの場合、バックスタックの高さは変わりません。バックスタックの一番下は、 必ず Main#1 、 Main#2 、または Main#3 のいずれかである必要があります。 バックスタックから自身を削除するには、 popUpTo の呼び出しが必要で、必要に応じて UI 状態の保存と復元が求められることがあります。 @Composable fun NavigationGraph() { val navController = rememberNavController() NavHost(navController, "splash") { composable("main3") { val viewModel: Main3ViewModel = hiltViewModel() Main3Screen( id = viewModel.id, navMain1 = { navController.navigate("main1") { popUpTo("main1") { inclusive = true saveState = true } launchSingleTop = true restoreState = true } }, navMain2 = { navController.navigate("main2") { popUpTo("main2") { inclusive = true saveState = true } launchSingleTop = true restoreState = true } }, navMain3 = { navController.navigate("main3") { popUpTo("main3") { inclusive = true saveState = true } launchSingleTop = true restoreState = true } }, navSub31 = { /* ... */ }, navSub32 = { /* ... */ }, navSub33 = { /* ... */ } ) } } } 一方向 このタイプのナビゲーションでは、前の画面に戻れない画面へ遷移します。バックスタックから自身を削除し( pop )、遷移先の画面を追加します ( push )。例えば、フォームを送信した後やスプラッシュ画面から移動した後に、元の画面へ戻れないケースなどが該当します。 戻れないようにするだけなら popBackStack で対応できますが、 必要に応じて popUpTo を使用し、バックスタックから複数の画面を削除することもできます。 @Composable fun NavigationGraph() { val navController = rememberNavController() NavHost(navController, "splash") { composable("splash") { val viewModel: SplashViewModel = hiltViewModel() SplashScreen( timeout = viewModel.timeout, navMain1 = { navController.popBackStack() navController.navigate("main1") } ) } composable("transactional3") { val viewModel: Transactional3ViewModel = hiltViewModel() Transactional3Screen( onClickSave = { /* ... */ }, onClickSubmit = { viewModel.onClickSubmit { navController.navigate("transactional1") { popUpTo("sub1") { inclusive = true } } } } ) } } } トランザクション(分割) このタイプのナビゲーションでは、非常に複雑または長い単一の画面を、複数のステップに分割します。伝える情報が多い場合や、ユーザーにアクションを求める場合に、ストレスを軽減してUXを向上させるために使用されます。ユーザーは途中でフローを終了できますが、再び入るときは最初から開始する必要があります。タスクを完了したり、フローから離れたりすると、フロー全体がバックスタックから削除されます。このアプローチは、フォームを含む複雑なUIの途中放棄を防ぐために、一方向ナビゲーションと組み合わせることができます。 フロー内では前後に自由に移動できますが、フローを終了するときは popUpTo を使ってバックスタックからフロー内のすべての画面を削除する必要があります。 @Composable fun NavigationGraph() { val navController = rememberNavController() NavHost(navController, "splash") { composable( route = "transactional1", arguments = listOf( navArgument("draft") { type = NavType.IntType } ) ) { val viewModel: Transactional1ViewModel = hiltViewModel() Transactional1Screen( id = viewModel.id, onClickBack = { viewModel.onClickBack { navController.popBackStack() } }, onClickSave = { viewModel.onClickSave { navController.navigate("sub2") { popUpTo("transactional1") { inclusive = true } } } }, onClickNext = { navController.navigate("transactional2") } ) } } } ナビゲーション管理 Sub#1 画面と、その関連する遷移(赤で強調)を使用して、オブジェクト指向の方法でナビゲーションを管理する方法を説明します。オブジェクト指向の要素を段階的に導入していきます。 Sub#1 画面には、次の条件があります。 Sub#1 画面を開くには、 draft パラメータ、または param1 、 param2 、 param3 、 Param4 パラメータの組み合わせが必要です。 Sub #1 画面から Sub #2 画面に移動するには、長時間実行される save 関数の完了を待つ必要があります。 Sub #1 画面から Transactional #1 画面に移動するには、長時間実行される start 関数の完了を待つ必要があります。 ViewModel はナビゲーショングラフやUIから独立している必要があります。ナビゲーションルートから直接受け取った引数を使用するか、それを元にデータを取得し、UI に必要なプロパティを設定する必要があります。 @HiltViewModel class Sub1ViewModel @Inject constructor( handle: SavedStateHandle, private val draftModel:DraftModel ) :ViewModel() { val id:UUID = UUID.randomUUID() val draft: Draft? = handle.get<Int?>("draft")?.let { runBlocking { if (null != draft && 0 < draft) { draftModel.get(draft)!!.also { param1 = it.param1 param2 = it.param2 param3 = it.param3 param4 = it.param4 } } } } var param1: String? = handle["param1"] private set var param2: String? = handle["param2"] private set var param3: String? = handle["param3"] private set var param4: String? = handle["param4"] private set fun onClickSave(callback: () -> Unit) { viewModelScope.launch { // Long save task. delay(2_000) callback() } } fun onClickStart(callback: () -> Unit) { viewModelScope.launch { // Long start task. delay(2_000) callback() } } } ガイドドキュメントの基本機能のみを使用する 「 ナビゲーショングラフの最小例 」ガイドを基に記述されたナビゲーションでは、 NavigationGraph に次の役割があります。 画面の登録 画面ルートのパラメータリストの宣言と登録 画面ナビゲーション用の route stringsの作成 NavController.Navigate のカプセル化 画面のUIを実行し、カプセル化された NavController.Navigate ロジックを渡す /** * ガイドドキュメント形式のナビゲーショングラフ */ @Composable fun NavigationGraph() { val navController = rememberNavController() NavHost(navController, "splash") { composable( route = "sub1?${ listOf( "draft={draft}", "param1={param1}", "param2={param2}", "param3={param3}", "param4={param4}" ).joinToString("&") }", arguments = listOf( navArgument("draft") { type = NavType.IntType defaultValue = 0 }, navArgument("param1") { type = NavType.StringType nullable = true }, navArgument("param2") { type = NavType.StringType nullable = true }, navArgument("param3") { type = NavType.StringType nullable = true }, navArgument("param4") { type = NavType.StringType nullable = true } ) ) { Sub1Screen( navBack = navController::popBackStack, navSub2 = { navController.navigate("sub2") { popUpTo("sub1") { inclusive = true } } }, navTransactional1 = { draft -> if (null == draft) { navController.navigate("transactional1") } else { navController.navigate("transactional1?draft=${draft.id}") } } ) } } } /** * ナビゲーション(カプセル化)、`ViewModel`(状態ホイスティング)、UI をつなぐ */ @Composable fun Sub1Screen( viewModel: Sub1ViewModel = hiltViewModel(), navBack: () -> Unit = {}, navSub2: () -> Unit = {}, navTransactional1: (Draft?) -> Unit = {} ) { Sub1Content( id = viewModel.id, param1 = viewModel.param1!!, param2 = viewModel.param2!!, param3 = viewModel.param3!!, param4 = viewModel.param4!!, onClickBack = navBack, onClickSave = { viewModel.onClickSave(callback = navSub2) }, onClickStart = { viewModel.onClickStart(callback = { navTransactional1(viewModel.draft) }) } ) } /** * 引数の状態を表示するだけ */ @Composable private fun Sub1Content( id: UUID, param1: String, param2: String, param3: String, param4: String, onClickBack: () -> Unit = {}, onClickSave: () -> Unit = {}, onClickStart: () -> Unit = {} ) { IconButton(onClick = onClickBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "back") } // ... Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { OutlinedButton( onClick = onClickSave, modifier = Modifier.padding(16.dp) ) { Text("SAVE", style = MaterialTheme.typography.labelLarge) } Button( onClick = onClickStart, modifier = Modifier.padding(16.dp) ) { Text("START", style = MaterialTheme.typography.labelLarge) } } } 進行中の課題 長いナビゲーショングラフコード : NavigationGraph のコードは、画面数、各画面の引数、接続された画面、バックスタック操作が増えるにつれて長くなります。 コード順と実行順 の不一致:コードが記述される順序は、そのまま読み込まれる順序になりますが、実行順とは一致しません。この不一致により、開発者は読み取っているコードが現在のコンテキストに関連しているかどうかを常に判断する必要があり、認知的オーバーヘッドが増え、メンテナンスが難しくなります。 UI関数のパラメータが多すぎる :UI関数( Sub1Screen )のパラメータ数は、接続される画面が増えるほど増加します。例えば、詳細な設定項目が多い設定画面では、それに応じてパラメータの数も増えてしまいます。 Navigator の導入 ナビゲーターを導入する目的は、UI 関数のパラメータ数を減らすことです。UI 関数のナビゲーション関連パラメータをナビゲーターオブジェクトにまとめます。 ナビゲータの役割: Sub#1 から移動可能な画面をオブジェクトにグループ化 :ナビゲーターオブジェクトは、 Sub#1 画面から遷移できる画面のナビゲーションロジックをカプセル化します。 ナビゲーション route の作成 :ナビゲーターオブジェクトが、ナビゲーションに必要な route stringsを生成します。 バックスタックの操作 :ナビゲーターオブジェクトは、画面の pop やpush などのバックスタック操作を管理し、適切なナビゲーションフローを維持します。 @Immutable class Sub1Navigator( private val navController: NavController ) { fun back() { navController.popBackStack() } fun sub2() { navController.navigate("sub2") { popUpTo("sub1") { inclusive = true } } } fun transactional1(draft: Drift? = null) { if (null == draft) { navController.navigate("transactional1") } else { navController.navigate("transactional1?draft=${draft.id}") } } } Navigator を導入し、ナビゲーションルートの作成とバックスタック操作コードを分離することで、 NavigationGraph がよりシンプルになりました。 composable( route = "sub1?${ listOf( "draft={draft}", "param1={param1}", "param2={param2}", "param3={param3}", "param4={param4}" ).joinToString("&") }", arguments = listOf( navArgument("draft") { type = NavType.IntType defaultValue = 0 }, navArgument("param1") { type = NavType.StringType nullable = true }, navArgument("param2") { type = NavType.StringType nullable = true }, navArgument("param3") { type = NavType.StringType nullable = true }, navArgument("param4") { type = NavType.StringType nullable = true } ) ) { Sub1Screen(navigator = remember(navController) { Sub1Navigator(navController) }) } Sub1Screen では、ナビゲーション関連のパラメータを 1 つのオブジェクトにまとめることでパラメータ数が減り、オブジェクトメンバーとしてカプセル化することでコールコードがより明確になります。 @Composable fun Sub1Screen( navigator:Sub1Navigator, viewModel:Sub1ViewModel = hiltViewModel() ) { Sub1Content( id = viewModel.id, param1 = viewModel.param1!!, param2 = viewModel.param2!!, param3 = viewModel.param3!!, param4 = viewModel.param4!!, onClickBack = navigator::back, onClickSave = { viewModel.onClickSave(callback = navigator::sub2) }, onClickStart = { viewModel.onClickStart(callback = { navigator.transactional1(viewModel.draft) }) } ) } 進行中の課題 UI 関数は簡素化されましたが、ナビゲーショングラフの複雑さは変わらず、開発者は次の役割のために継続的なコンテキスト切り替えを求められます。 画面の登録 画面ルートのパラメータリストの宣言と登録 Navigator コンパニオンオブジェクト NavigationGraph の機能を簡素化し、ナビゲーション情報を一元化するために、Navigatorクラス内で コンパニオンオブジェクト を使用できます。このアプローチにより、routes と argumentsを1か所で定義できます。方法は次のとおりです。 Navigator クラスの コンパニオンオブジェクト内 でroutes と argumentsを定義する。 これらの定義を、 NavigationGraph 関数で使用する。 @Immutable class Sub1Navigator( private val navController: NavController ) { @Suppress("MemberVisibilityCanBePrivate") companion object { const val ARG_DRAFT = "draft" const val ARG_PARAM1 = "param1" const val ARG_PARAM2 = "param2" const val ARG_PARAM3 = "param3" const val ARG_PARAM4 = "param4" const val ROUTE = "sub1?" + "$ARG_DRAFT={draft}&" + "$ARG_PARAM1={param1}&" + "$ARG_PARAM2={param2}&" + "$ARG_PARAM3={param3}&" + "$ARG_PARAM4={param4}" val ARGUMENTS = listOf( navArgument(ARG_DRAFT) { type = NavType.LongType defaultValue = 0 }, navArgument(ARG_PARAM1) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM2) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM3) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM4) { type = NavType.StringType nullable = true } ) } // ... } ナビゲータークラスで コンパニオンオブジェクト を使用することで、ナビゲーショングラフを単純化し、画面登録と UI 関数の呼び出しに集中できるようになります。UI 関数自体は変更されません。 composable( route = Sub1Navigator.ROUTE, arguments = Sub1Navigator.ARGUMENTS ) { Sub1Screen(navigator = remember(navController) { Sub1Navigator(navController) }) } 進行中の課題 Sub#1 画面のみを考慮する場合、これで十分です。しかし、 Sub #1 画面に移動する必要がある Main #1 や Sub #2 のナビゲーターも含める場合、次のような対応が必要になります。 @Immutable class Main1Navigator( private val navController: NavController ) { // ... fun sub1(item:Main1Item) { navController.navigate("sub1?param1=${item.param1}&param2=${item.param2}&param3=${item.param3}&param4=${item.param4}") } } @Immutable class Sub2Navigator( private val navController: NavController ) { // ... fun sub1(draft: Draft) { navController.navigate("sub1?draft=${draft.id}") { popUpTo(Main2Navigator.ROUTE) { inclusive = true } } } } Sub1Navigator は有効な route フォーマットを管理しますが、実際の route の作成は Main1Navigato と Sub2Navigator が処理するため、 Sub#1 のroute管理が Sub#1 に集中せず、ユーザー側に分散してしまうという一貫性のない状態になります。 共有宛先の処理 有効な route フォーマットと、有効な route 値を構成するロジックを、一緒に管理するのが妥当です。各 companion object に route 作成ロジックを移動させることで、標準化できます。 route 自体とその組み立てをカプセル化する。 オブジェクトベースのナビゲーションを実装する。 @Immutable class Sub1Navigator( private val navController: NavController ) { companion object { // ... const val ARG_DRAFT = "draft" const val ARG_PARAM1 = "param1" const val ARG_PARAM2 = "param2" const val ARG_PARAM3 = "param3" const val ARG_PARAM4 = "param4" fun route(item:Main1Item) = "sub1?$ARG_PARAM1=${item.param1}&$ARG_PARAM2=${item.param2}&$ARG_PARAM3=${item.param3}&$ARG_PARAM4=${item.param4}" fun route(draft:Draft) = "sub1?draft=${draft.id}" } // ... } @Immutable class Main1Navigator( private val navController: NavController ) { // ... fun sub1(item: Main1Item) { navController.navigate(Sub1Navigator.route(item)) } } @Immutable class Sub2Navigator( private val navController: NavController ) { // ... fun sub1(draft: Draft) { navController.navigate(Sub1Navigator.route(draft)) { popUpTo(Main2Navigator.ROUTE) { inclusive = true } } } } 進行中の課題 画面を登録する際は、 route 、 arguments 、そしてナビゲーターインスタンスの作成を同じクラス内でまとめて管理することが重要です。もし誤りがあると、次のような論理エラーが発生する可能性があります。 不整合なルート定義 :routeとargumentsが一貫して定義されていない場合、ナビゲーションが失敗したり、予期しない動作をする可能性があります。 不正なナビゲーターインスタンス :異なる ナビゲーターインスタンスを使用すると、ナビゲーションロジックのエラーが発生し、アプリが誤った画面に遷移したり、ナビゲーションが機能しなくなったりする可能性があります。 composable( route = Main1Navigator.ROUTE, arguments = Transactional1Navigator.ARGUMENTS ) { Sub1Screen(navigator = remember(navController) { Sub1Navigator(navController) }) } Navigator、 コンパニオンオブジェクト の抽象化、および宛先処理の標準化 ナビゲーターとその コンパニオンオブジェクト を抽象化し、次のプロパティを定義できます interface Navigator { val destination: Destination } interface Destination { val routePattern: String val arguments: List<NamedNavArgument> fun route(varargs arguments: Any?): String } ナビゲーターと コンパニオンオブジェクト がそれぞれ Navigator と Destination を実装している場合、ナビゲーショングラフの設定は次のように標準化されます。 @Immutable class Sub1Navigator( private val navController: NavController ):Navigator { companion object:Destination { const val ARG_DRAFT = "draft" const val ARG_PARAM1 = "param1" const val ARG_PARAM2 = "param2" const val ARG_PARAM3 = "param3" const val ARG_PARAM4 = "param4" override val routePattern = "sub1?$ARG_DRAFT={draft}&$ARG_PARAM1={param1}&$ARG_PARAM2={param2}&$ARG_PARAM3={param3}&$ARG_PARAM4={param4}" override val arguments = listOf( navArgument(ARG_DRAFT) { type = NavType.LongType defaultValue = 0 }, navArgument(ARG_PARAM1) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM2) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM3) { type = NavType.StringType nullable = true }, navArgument(ARG_PARAM4) { type = NavType.StringType nullable = true } ) override fun route(varargs arguments: Any?): String = when { 1 == arguments.size && arguments[0] is Main1Item -> route(arguments[0] as Main1Item) 1 == arguments.size && arguments[0] is Draft -> route(arguments[0] as Draft) else -> throw IllegalArgumentException("Invalid arguments : arguments=$arguments") } fun route(item: Main1Item) = "sub1?$ARG_PARAM1=${item.param1}&$ARG_PARAM2=${item.param2}&$ARG_PARAM3=${item.param3}&$ARG_PARAM4=${item.param4}" fun route(draft: Draft) = "sub1?draft=${draft.id}" } override val destination = Companion } NavigationGraph の役割は次のとおりです。  ナビゲーターインスタンスの作成 抽象化されたナビゲーションオブジェクトをUI関数と接続する。 Main1Navigator(navController).let { navigator -> composable(navigator.destination.routePattern, navigator.destination.arguments) { Main1Screen(navigator) } } Sub1Navigator(navController).let { navigator -> composable(navigator.destination.routePattern, navigator.destination.arguments) { Sub1Screen(navigator) } } Sub2Navigator(navController).let { navigator -> composable(navigator.destination.routePattern, navigator.destination.arguments) { Sub2Screen(navigator) } } 開発の生産性向上 グローバルナビゲーション Web ブラウザの起動、電話の発信、アプリ設定を開く、アプリの再起動、UI の再読み込みなどは、画面に関係なく必要になることがあります。これらの 共通機能 を、各画面ごとに個別実装するよりも、単一の実装を共有する方が効率的です。 @Immutable class Sub1Navigator( private val navController: NavController ): Navigator { fun web(uri: Uri) { /* Indivisual impl */ } fun call(phoneNumber: String) { /* Indivisual impl */ } } @Immutable class Sub31Navigator( private val navController: NavController ): Navigator { fun web(uri: Uri) { /* Indivisual impl */ } fun settings() { /* Indivisual impl */ } } ソリューション Navigator インターフェースで共通関数を宣言し、ユニバーサル関数を実装した上で、各スクリーンのナビゲーターに委任することで共通化を実現できます。 /** * 共通のナビゲーション関数を定義するインターフェース */ interface Navigator { val destination: Destination fun web(uri: Uri) fun call(phoneNumber: String) fun settings() fun reopen() fun restart() } /** * 共通のナビゲーション関数を実装する基底クラス */ open class BaseNavigator( private val activity: Activity, val navController: NavController ): Navigator { override fun web(uri: Uri) { activity.startActivity(Intent(ACTION_VIEW, uri)) } // ... override fun reopen(){ activity.finish() activity.startActivity(Intent(activity, activity::class.java)) } override fun restart() { activity.startActivity(Intent(activity, activity::class.java)) exitProcess(0) } } /** * 各画面のナビゲーターが共通のナビゲーション機能を委譲 */ @Immutable class Sub1Navigator( private val baseNavigator: BaseNavigator ): Navigator by baseNavigagor { fun sub2() { baseNavigator.navController.navigate(Sub2Navigator.route()) { popUpTo(routePattern) { inclusive = true } } } } @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { NavigationGraph(this@MainActivity) } } } /** * `NavController` を `BaseNavigator` インスタンスに置き換える * * @param activity ナビゲーショングラフを所有するアクティビティ */ @Composable fun NavigationGraph(activity: Activity) { val baseNavigator = BaseNavigator(activity, rememberNavController()) NavHost(baseNavigator.navController, SplahNavigator.routePattern) { Sub1Navigator(baseNavigator).let { navigator -> // ... } } } ナビゲーショングラフ設定ユーティリティ 以下のような論理的エラーのリスクがあります。 Sub1Navigator(baseNavigator).let { navigator -> composable(Main1Navigator.routePattern, Transactional1Navigator.arguments) { Sub1Screen(navigator) } } ソリューション ナビゲーターインスタンスと画面登録を処理するユーティリティ関数を追加することで、 fun <N :Navigator> NavGraphBuilder.composable( navigator: N, content: @Composable AnimatedContentScope.(NavBackStackEntry, N) -> Unit ) { composable( route = navigator.destination.routePattern, arguments = navigator.destination.arguments ) { backStackEntry -> content(backStackEntry, navigator) } } ナビゲーショングラフの構成を単純化し、論理エラーの可能性を排除することができます。 composable(Main1Navigator(baseNavigator)) { _, navigator -> Main1Screen(navigator) } composable(Sub1Navigator(baseNavigator)) { backStackEntry, navigator -> Sub1Screen(navigator) } composable(Sub2Navigator(baseNavigator)) { _, navigator -> Sub2Screen(navigator) } @Preview のサポート 静的リソースをバンドルで表示する画面では、引数として別のイベントハンドラを渡すのではなく、ナビゲーターのみを渡すことで ナビゲーションコードを記述できます。 例: @Composable fun StaticResourceListScreen(navigator: StaticResourceListNavigator) { Column { Button(onClick = navigator::static1) { Text("Static#1") } Button(onClick = navigator::static2) { Text("Static#2") } Button(onClick = navigator::static2) { Text("Static#2") } } } @Preview コードでは、 Navigatorインスタンスごとに BaseNavigator(PreviewActivity(), rememberNavController()) を繰り返し記述することになります。 プレビューの種類や数が増えると、プレビューの記述が面倒になります。そして、このオーバーヘッドが原因で開発者がプレビューをスキップしてしまうこともあります。 @Composable @Preview(showSystemUi = true) fun PreviewStaticResourceListScreen() { MaterialTheme { StaticResourceListScreen(StaticResourceListNavigator(BaseNavigator(PreviewActivity(), rememberNavController()))) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic1Screen() { MaterialTheme { Static1Screen(Static1Navigator(BaseNavigator(PreviewActivity(), rememberNavController()))) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic2Screen() { MaterialTheme { Static2Screen(Static2Navigator(BaseNavigator(PreviewActivity(), rememberNavController()))) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic3Screen() { MaterialTheme { Static3Screen(Static3Navigator(BaseNavigator(PreviewActivity(), rememberNavController()))) } } ソリューション BaseNavigator をインスタンス化するユーティリティ関数を作成し、実際のアプリケーションとプレビューを個別に処理 できるように実装します。 @Composable fun baseNavigator( activity: Activity = if (LocalInspectionMode.current) { PreviewActivity() } else { LocalContext.current as Activity } ):BaseNavigator { val navHostController = rememberNavController() val base = remember(activity) { BaseNavigator(activity, navHostController) } return base } @Composable fun NavigationGraph(activity: Activity) { val baseNavigator = baseNavigator(activity) NavHost(navController, SplahNavigator.routePattern) { // ... } } @Composable @Preview(showSystemUi = true) fun PreviewStaticResourceListScreen() { MaterialTheme { StaticResourceListScreen(StaticResourceListNavigator(baseNavigator())) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic1Screen() { MaterialTheme { Static1Screen(Static1Navigator(baseNavigator())) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic2Screen() { MaterialTheme { Static2Screen(Static2Navigator(baseNavigator())) } } @Composable @Preview(showSystemUi = true) fun PreviewStatic3Screen() { MaterialTheme { Static3Screen(Static3Navigator(baseNavigator())) } } カスタムスタート画面 これまで使用していた NavigationGraph 設定機能は、スタート画面の変更は可能ですが、使用できるナビゲーショングラフは 1 つのみ です。そのため、既存の機能の一部だけを活用した デモアプリケーションの開発はできません。 @AndroidEntryPoint class DemoActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { NavigationGraph(this@DemoActivity) // 固定されたスタート画面  } } } @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { NavigationGraph(this@MainActivity) } } } @Composable fun NavigationGraph(activity: Activity) { val baseNavigator = baseNavigator(activity) NavHost(navController, SplahNavigator.routePattern) { Sub1Navigator(baseNavigator).let { navigator -> composable(navigator.destination.routePattern, navigator.destination.arguments) { Sub1Screen(navigator) } } } } ソリューション BaseNavigator に destination を実装し、アクティビティからホイストされた BaseNavigator インスタンスをナビゲーショングラフに渡します。 open class BaseNavigator( private val activity: Activity, val navController: NavController, override val destination: Desination ):Navigator { // ... } /** * @param startDestination プレビュー時のみ使用可能なデフォルト値 */ @Composable fun baseNavigator( activity: Activity = if (LocalInspectionMode.current) { PreviewActivity() } else { LocalContext.current as Activity }, startDestination: Destination = if(LocalInspectionMode.current || activity is PreviewActivity) { object: Destination { override val routePattern = "preview" // ... } } else { throw IllegalArgumentException ("本番環境では `startDestination` を指定する必要があります。") } ): BaseNavigator { val navHostController = rememberNavController() val base = remember(activity) { BaseNavigator(activity, navHostController) } return base } @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { NavigationGraph(baseNavigator(destination = SplashNavigator.Companion)) } } } @Composable fun NavigationGraph(baseNavigator: BaseNavigator = baseNavigator()) { //追加の共通ナビゲーション機能を実装 NavHost( // Encapsulate the `navigation-compose` dependency in activity(`MainActivity`) and UI(`UiRoot`). baseNavigator.navController, baseNavigator.destination.routePattern ) { // ... } } DEMOアプリ開発サポート ナビゲーショングラフの設定機能( NavigationGraph )では、スタート画面の変更は可能ですが、使用できるナビゲーショングラフとスタート画面は 1 つのみです。そのため、既存の機能の一部だけを活用した デモアプリケーションの開発はできません。 @Composable fun NavigationGraph(baseNavigator: BaseNavigator = rememberBaseNavigator()) { NavHost( navController = baeNavigator.navController, startDestination = baseNavigator.destination.routePattern ) { // ... } } ソリューション ナビゲーショングラフ構成コードを、 navigation-compose のコールから分離します。これにより、ナビゲーショングラフの構築、ナビゲーターと UI 機能の接続、共通ナビゲーション機能の分離 が可能になり、さらに 依存関係のカプセル化 も実現できます。 @Composable fun NavigationGraph(baseNavigator: BaseNavigator = rememberBaseNavigator(), builder: NavGraphBuilder.() -> Unit) { NavHost( navController = baeNavigator.navController, startDestination = baseNavigator.destination.routePattern, builder = builder ) } プロダクションアプリ @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { UiRoot(baseNavigator(destination = SplashNavigator.Companion)) } } } @Composable fun UiRoot(baseNavigator: BaseNavigator) { NavigationGraph(baseNavigator) { // ... composable(Sub1Navigator(baseNavigator)) { _, navigator -> Sub1Screen(navigator) } // ... } } デモアプリ @AndroidEntryPoint class DemoActivity : ComponentActivity() { override fun onCreate(savedInstanceState:Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { DemoUiRoot(baseNavigator(destination = Transactional1Navigator.Companion)) } } } @Composable fun DemoUiRoot(baseNavigator: BaseNavigator) { NavigationGraph(baseNavigator) { composable(Transactional1Navigator(baseNavigator)) { _, navigator -> Transactional1Screen(navigator) } } } 用語の統一 ガイドドキュメント では、 screen という用語を ナビゲーションターゲット を指すために使っています。しかし、多くの UI デザインシステム(例: Atomic Design 、 Carbon Design System 、 Ant Design 、 Shopify Polaris など)では、 ページ を使い、ナビゲーションターゲットに使用し、 screen は物理デバイスのディスプレイに使用します。 さらに、ナビゲーショングラフに画面を登録するとき、関数名 composable は @Composable 関数が呼び出されたことを示すために使用されます。 開発者向けの専門用語を 開発以外の分野でも通じる表現に統一することで、混乱や意味の確認が不要になり、スムーズで素早いコミュニケーション ができるようになります。そのため、用語の統一が望ましいというわけです。 fun <N : Navigator> NavGraphBuilder.composable( navigator: N, content: @Composable AnimatedContentScope.(NavBackStackEntry, N) -> Unit ) { composable( route = navigator.destination.routePattern, arguments = navigator.destination.arguments ) { entry -> content(entry, navigator) } } @Composable fun UiRoot(baseNavigator: BaseNavigator) { NavigationGraph(baseNavigator) { composable(Sub1Navigator(baseNavigator)) { _, navigator -> Sub1Screen(navigator) } } } @Composable fun Sub1Screen(navigator: Sub1Navigator, viewModel:Sub1ViewModel = hiltViewModel()) { // ... } ソリューション 設計システムとして Atomic Design を採用していると仮定し、ナビゲーションターゲットには screen ではなく page を使用する形で用語を統一します。 fun <N : Navigator> NavGraphBuilder.page( navigator: N, content: @Composable AnimatedContentScope.(NavBackStackEntry, N) -> Unit ) { composable( route = navigator.destination.routePattern, arguments = navigator.destination.arguments ) { entry -> content(entry, navigator) } } @Composable fun UiRoot(baseNavigator: BaseNavigator) { NavigationGraph(baseNavigator) { page(Sub1Navigator(baseNavigator)) { _, navigator -> Sub1Page(navigator) } } } @Composable fun Sub1Page(navigator: Sub1Navigator, viewModel: Sub1ViewModel = hiltViewModel()) { // ... } 結論 最初は、ガイドドキュメントの基本機能だけを使っていたため、構造はシンプルでした。 オブジェクト指向ナビゲーション設計を取り入れることで、特定のナビゲーション仕様に影響されることなく、それぞれが特殊機能を担当する複数のジェネリック型や機能を実現できるようになりました。 Navigator :UI に関連する グループナビゲーション を管理し、ナビゲーショングラフにどのように登録されるかを決めるプロパティを定義する。 Destination :ナビゲーショングラフにページを登録するためのプロパティを定義する。 BaseNavigator : Application 、 Activity 、およびナビゲーショングラフの統合を定義し、共通・グローバルナビゲーションを実装する。 NavGraphBuilder.page :ナビゲーショングラフと各ページを Navigator ) を通じて接続する。 @Composable fun NavigationGraph : navigation-compose をカプセル化し、共通のナビゲーション機能を実装することで、共通機能と具体的なナビゲーショングラフを分離する。 @Composable fun baseNavigator :開発の利便性を向上させる。 ジェネリックオブジェクトの構造は以下のとおりです。 汎用オブジェクトを使用してナビゲーショングラフを構築すると、どうしても複雑な構造になりがちです。しかし、すでに汎用的な機能が整備されている場合、アプリケーションレベルの開発では考慮すべき要素が減るため、開発期間を短縮できます。その結果、新しい画面を追加するときの平均的な開発コストが低下します。 この設計アプローチが、 すべての分野、製品、開発環境、個人に必要というわけではありません。必要に応じて、優先度を下げたり、別の解決策を選択することが求められます。本記事で紹介しているオブジェクト指向設計あくまで一例です。ただ、ここで紹介した設計上の課題や方向性は、他のソフトウェア開発にも応用可能です。 オブジェクト指向設計とは? オブジェクト指向設計は、必ずしもコードを短くし、シンプルな構造にするためのものではありません。コードは役割ごとに分割されている場合にのみ、見た目がシンプルになります。例えば、このガイドの手法に従っても、ナビゲーショングラフ全体を構築する機能と各画面の機能は別々に記述することになります。極端な話、すべての画面を NavigationGraph 内に実装すること可能ですし、 NavigationGraph をまったく使わずに、 MainActivity では setContent を呼び出してすべての画面を構築することもできます。 オブジェクト指向設計は、コードの役割と、その機能を実現するために必要な責務を分析することから始まります。そこから何をグループ化し、どのようにタイプやファイルに分割すべきかを決めます。各コードは、 import またはオブジェクト参照を通じて接続されています。最終的に、どの開発者がコードを管理し、それに関する調査や保守の責任を負うのかが決まります。 IT業界において、開発者は非常に重要な生産リソースです。そのため、コストと生産性は開発者がどのように時間を使うかに大きく左右されます。このことから、開発者の認知能力は貴重なリソースとなります。技術スキルが高く、製品への理解が深まり、作業履歴に関する知識が豊富であるほど、開発者の集中力が生み出す価値も高まります。現在見ているコードが今のタスクに必要かどうかを判断する認知オーバーヘッド は、最も価値の低い形で開発者の集中力(認知能力)を消費してしまいます。集中力が低下すると、生産性も落ちてしまいます。 オブジェクト指向設計を適用すると、必然的に構造が複雑になり、コードを正しく理解するために 深い技術的知識や過去の開発履歴に関する知識が必要になります。 オブジェクト指向設計_は、表面レベルの機能を迅速かつ容易に開発できる環境を整えるために、事前に大規模な開発と学習を必要とする開発手法です。 オブジェクト指向設計のリスクとコスト オブジェクト指向設計は、大量の汎用機能と複雑な構造 を活用し、高い生産性を実現するための開発手法です。しかし、汎用的な機能や構造には限界があります。そして、これらの制限を超える変更は、いつ発生してもおかしくありません。開発者ではないプランナー、マネージャー、デザイナーは、暗黙の知識(ドメイン知識)を日常的に習得・活用しているわけではありません。そのため、一見シンプルに思える変更が、なぜ実際には難しく、時間がかかるのかを理解するのはほぼ不可能 です。開発者同士であっても、暗黙知を持っていない人は、それを理解するのが難しいと感じるでしょう。この問題は、個人が暗黙知を身につけることや、開発者が繰り返し説明して理解してもらうことで解決できるものではありません。代わりに、これは個人の問題ではなく、組織や企業全体での体系的かつ文化的な解決策が求められる課題です。 特に、企業や組織の生産性向上を目的としてオブジェクト指向設計を導入する場合、開発者同士や、開発者と他の職種との間で生じる暗黙知の違いによる衝突を管理することが重要になります。そして、そこにリスク(またはコスト)が発生します。もちろん、オブジェクト指向設計が適切に機能している場合は問題になりません。その前に、誰がオブジェクト指向設計を担当するのか、そして 組織や企業レベルで責任者をどのように決定するのかが大きな課題となります。もしこの判断を誤ると、組織や企業にとって最も貴重なリソースである「チームワーク」 が損なわれる可能性があります。 参考 Design your navigation graph / Minimal example android/compose-samples > Jetnews > HomeScreens Atomic Design Carbon Design System Ant Design Shopify Polaris
アバター
👋 はじめに こんにちは! Toyota Community by KINTO プラットフォームのプロダクトデザイナーのMoji です。 今回は、このプラットフォーム作成の裏話(コラボレーション、イノベーション、用途に合わせたデザインの道のり)についてご紹介できて嬉しいです。 私にとって、デザインは美しさを追求するだけのものではなく、課題を解決しながら、ユーザーのニーズとビジネスの目標をうまく調和させることだと思います。そういったわけで、Toyota Community by KINTOは私にとってとても刺激的なプロジェクトなのです。 Toyota Community by KINTO のロゴと、プラットフォーム機能を示すモバイル インターフェースのモックアップ 🚀 背景 Toyota Community by KINTO は、 ハッカソンで生まれたシンプルなアイデア から始まり、今ではレスポンスに優れた Webアプリに進化しています。現在、 トヨタの顧客やトヨタ車愛好家たちをひとつのグローバルなプラットフォームでつなぐことを目指して、顧客エンゲージメントを高めるための拡張性のあるソリューションとして提案できるよう、準備を進めています。 過去 2年間、このプロジェクトでは、単純に 設計と開発 に取り組んでいたのではありません。 社内のフィードバックに対応 し、 国や地域による市場の微妙な違い を理解し、 事業目標とユーザーのニーズ の両方に沿った製品を継続的に改良する。このプロセスの連続でした。 2022年 KINTO Global Innovation Days より。優勝したハッカソンチーム、チームコラボレーション、イノベーター・オブ・ザ・イヤー賞の盾の写真。 💡 チャレンジ 分散構造とバリューチェーンのギャップ 自動車業界では、分散型構造で運営されているブランドもあります。このアプローチでは、国や地域で異なる市場ニーズに柔軟かつ的確に対応できるという利点があります。一方で、 グローバルなブランドとしての一貫性を保つことや、購入後の顧客とのつながりを効果的に維持する点 では、課題が生じることもあります。 ここで、集中型ハブを構築する絶好のチャンスが姿を現します。このハブを通じて、ブランドは貴重な顧客の本音をくみ取り、ポジティブかつ影響力のあるかたちでブランドイメージを強化することができます。 競合分析 オンラインコミュニティの現状をより深く理解するために、オンライン上にある従来のカーコミュニティや競合プラットフォームを、 包括的に分析 しました。 重要な気づき 🚗 トヨタ車愛好家の中でも、特に旧車、または復刻モデルの所有者たちは、 Toyota Nation (VerticalScope が運営)や Reddit といった外部プラットフォームにアクセスして、交流したり、フィードバックを共有したり、自分の愛車について語り合っています。 🌐 多くのプラットフォームではすでにコミュニティスペース(フォーラム)が用意されていて、ユーザー同士がつながり、意見を交換し、互いに学び合っているのです。 ですが、その中でひときわ目立ったあるギャップがありました。 車好きは、自分の愛車を披露することにとても強い情熱を持っています。 にもかかわらず、車の所有者が自分専用のバーチャル・ガレージを作って、次のようなことを実現できる特別な場所がなかったのです。 🛠️ 改造やカスタマイズの紹介 📸 車の写真の共有 チャンスの特定 次の3つの戦略的な統合で、私たちのプラットフォームは他のライバルと一線を画す存在になりました。単なる 閉ざされたソーシャル空間 ではなく、ユーザーが本物のコンテンツに触れたり、それについて語り合えるゲートウェイにもなりました。 1.ミスター・ランクルとのつながり TOYOTAの幅広いネットワークの一つとして、トヨタのランドクルーザーの元チーフエンジニアで、 ミスター・ランクル としても知られる 小鑓貞嘉氏 とつながる機会に恵まれました。 このご縁を通じて、 限定動画 および専用の Ask Me Anything (AMA) チャンネルを特集することができました。ユーザーはこのチャンネルでミスター・ランクルと直接やり取りをしたり、質問を投げかけたりしながら、貴重な気づきを得ることができます。 ミスター・ランクルこと小鑓貞嘉さんとのコラボレーション。チームディスカッションや撮影風景など。 2.トヨタイムズから信頼できる自動車ニュースを共有 さらに、信頼性の高い自動車ニュースにも価値があることから、トヨタのオウンドメディアとして知られている トヨタイムズ と交渉し、公式に掲載している記事を当社のWebアプリ上で共有することについて、許可をいただきました。 これにより、ユーザーは 信頼できるブランド 発のコンテンツ にアクセスできるだけでなく、自分自身のストーリーを共有したり、有意義なディスカッションに参加したりできるようになりました。 Toyota Community by KINTO プラットフォーム、ランドクルーザーに関するトヨタイムズ記事スクリーンショット 3.「ガレージ」機能のご紹介 車好きにとって、自分の愛車を披露したり、時間をかけて仕上げたカスタマイズや改造を共有したり、同じ情熱を持つ人たちとつながったりすることは、かけがえのない楽しみのひとつですよね。その気持ち、私たちもよく分かります。 そこで、「ガレージ」機能を導入しました。車のオーナーが、より楽しく、自分らしく愛車を紹介できるようになっています。この機能では、次のようなことができます。 愛車の写真を紹介 カスタマイズや改造の記録、印象的な出来事の記録 自分だけのストーリーを、魅力的なインターフェース上で共有 Toyota Community by KINTOプラットフォーム。バーチャルガレージ内、ユーザーの1981年型ランドクルーザーFJ40ソフトトップのスクリーンショット。改造の詳細や思い出とともに紹介されています。 🎨 ソリューションの設計 ディストリビューターのフィードバック 実用最小限の製品 (MVP)の開発に入る前に、まずは主要な関係者から早期のフィードバックを得ることを目的として、モックアップとプロトタイプを設計しました。得られた意見をもとに何度も改良を重ねて、完成版のプロトタイプをいくつかのマーケットの地域ディストリビューターに提示し、さらなるフィードバックを募りました。 Toyota Community の初期モックアップ。上は、デザイン案に対するフィードバック募るために提案された機能。 受け取ったフィードバックを踏まえ、プラットフォームのデザインを新たなビジネス目標に沿う形に調整し、他と差別化できるポイントを強調しました。 こうして、より多くの時間やリソースを投じる前に概念実証(PoC)を確保することを目標に、MVPの本格的な開発に着手することができたのです。 🛠️ 第1段階 - 開発 実用最小限の製品(MVP)の作成 限られたリソースの中で、ギャップを解消して既存のプラットフォームにはない価値を提供するMVPを設計・開発しました。このプロダクトは、2つの主要機能を軸に、2024年7月に誕生しました。 💬 コミュニティセクション トヨタの文化について語り合えるディスカッションスペースです。ユーザーはスレッドを立てたり、絵文字でリアクションを送ったりしながら交流できます。 ミスター・ランクル専用チャンネル: ミスター・ランクルのガレージ、これまでの体験、インタビューなどを特集した限定コンテンツです。 ミスター・ランクルに何でも質問できる専用チャンネルで、愛好家がミスターと直接交流することができます。 トヨタニュースチャンネル: トヨタについて、信頼できる自動車ニュースを共有する専用チャンネル。 🚘 バーチャルガレージ ユーザーが自分の愛車を写真やストーリーとともに紹介し、ほかのユーザーと共有できる専用スペースです。 Figmaのデザイン画面、各デザイン段階とマイルストーンを示すタイムライン。 Toyota Community by KINTO プラットフォーム上のガレージ、ホーム、コミュニティの3セクションのモバイル画面 ユーザーからのフィードバック収集 製品にとって重要な転機となったのは、当社プロダクトオーナーが、 ドイツで開催されたランドクルーザーのイベント に参加したときでした。 トヨタ車愛好家との直接の交流を通じて、非常に貴重な気づきを得ることができました。参加者からは、小鑓氏のようなトヨタの象徴的な人物と交流しながら、自分の車を披露できるスペースがあるというアイデアに対して、好評の声が寄せられました。 ドイツで開催されたランドクルーザーイベント。改造車両、ランドクルーザーのオーナーや愛好家の集合写真。 このイベントの重要性を実感したことで、私たちはユーザーの記憶に残る、強い存在感というものを築こうと決めました。 KINTOとトヨタ、両ブランドのアイデンティティに自然に調和するロゴとマーケティング資料を作成するため、関係者と緊密に連携しながら、デザインをリードしました。 Toyota Community by KINTOのプロモーション資料のコレクション。バーチャルガレージとコミュニティのメリットを宣伝するチラシを掲載しています。 ユーザーからのフィードバックと、直前でのブランディング作業がうまくかみ合ったことで、 プロダクトの方向性 と ビジュアル アイデンティティ の両方が洗練されて、道筋がぐっと明確になりました。 🔄 第2段階- 改良 導入と拡張 大規模なユーザーに向けてMVPをテストするだけのリソースは、まだ十分にありません。ですが、 ディストリビューターのフィードバックやランドクルーザーイベントで得られた気づきをもとに、プラットフォームの改善は今も着実に進んでいます 。こうした改良が、次の開発段階への足がかりになっています。 Toyota Community by KINTO プラットフォームは、拡張性のあるソリューションとして位置付けられつつあり、断片化されたバリューチェーンに関する課題に応えられる可能性を示しながら、地域ごとの多様なマーケットにも柔軟に対応できる仕組みを備えています。 この先にあるものは? 私たちは今後も、プラットフォームのブラッシュアップを進めながら、さらなる検討に向けて価値をしっかりと伝えられる機会を探っていきます。 ✨ 結論 トヨタの取り組みが広がる未来へ Toyota Community by KINTO は、グローバルなエンゲージメントをひとつの集中型ハブに集約し、再びつなげていくチャンスを提供しています。 さらにリソースを得られれば、このプラットフォームは貴重な資産となり、顧客エンゲージメントを高めるだけでなく、より強力なブランドロイヤルティの機会を育んでいける可能性を秘めています。 このプロジェクトは、単に UX デザイン や 機能の構築 が目的ではありませんでした。ユーザー中心のデザインが持つ真の力。それは、ビジネスの目標にも、文化的なニーズにも 適応 していく力なのだということを実感しました。 この記事を楽しんでいただけましたら、ぜひ私の過去の記事もご覧ください。 多様性とインクルーシブな視点の重要性 や、 TechBlog の再設計プロセス ついても詳しく書いています。
アバター
The very first Appium Meetup Tokyo was held on February 20, 2025, at Autify's Tokyo Office. Around 10 people attended in person on the day, with many more joining online. Opening and Icebreaker The event started with a lively and humorous opening talk by tsueeemura -san from Autify. To check the online connection and break the ice, he asked, "What's everyone having for dinner tonight?" An attendee responded, "Hot pot tonight," which brought a warm and relaxed atmosphere. Autify's Appium Plugin Use Case The first speaker was rerorero -san, who is in charge of mobile product development at Autify. "Autify NoCode Mobile" provided by Autify is a cloud-based service that enables easy mobile app test automation without writing any code. Even without programming knowledge, you can record tests through an intuitive interface and have them automatically re-executed. The biggest advantage of this is that even developers and non-engineers can quickly set up a testing environment. Another key feature is the ability to use real devices and simulators in the cloud, eliminating the need for in-house equipment and significantly reducing capital investment. However, there was a performance issue where the app would significantly slow down on screens with a large number of UI elements. Tap operations that normally took just a few seconds could take up to 40 seconds. To tackle this problem, rerorero-san introduced " IDB (iOS Development Bridge) " developed by Facebook. IDB is an open-source CLI tool that allows fast interaction with iOS simulators and real devices by sending events directly to the Core Simulator Service, dramatically improving response speed. By integrating IDB as an Appium plugin and building a system that can be used directly without complex network settings between servers, Autify succeeded in reducing a 40-second operation to just 40 milliseconds, achieving roughly a 1,000x performance improvement. Key Points from the presentation How to adopt the Appium plugin and implementation examples using JavaScript The technical mechanism behind IDB's fast tapping (sending events directly to the Core Simulator Service) A practical demonstration of performance improvements The plugin can be implemented using the following code: KINTO Technologies: Guidelines and Practices for Efficient App Test Automation Next, Oka-san and Pann Nu-san from KINTO Technologies presented their method for building an automated testing environment and the results they achieved. At our company, the growing number of device and OS combinations had significantly increased the burden of manual testing. To address this, the QA and development teams began collaborating from the early stages of development and introduced a method for setting unified, dedicated test IDs. This greatly reduced the burden of modifying XPATH when changing layouts, and significantly improved test stability. In addition, we also built a system where test results are notified in real time via Slack, while managing detailed logs and test videos through Box. This creates an environment where all involved parties can easily check the test status. Key Points from the presentation Integrating automated testing awareness into the development process Comparing maintenance load before and after introducing dedicated test IDs How to efficiently manage test results using Slack and Box Improving coding efficiency using Github Copilot Presentation Materials https://speakerdeck.com/kintotechdev/xiao-lu-de-naapurizi-dong-hua-notamenogaidoraintoshi-jian-fang-fa 📌 Current Status of E2E Testing and Topics of Interest from the Participant Survey This time, we conducted a survey of everyone who attended the Meetup. Here are some interesting trends that emerged from the results. (1) Participants by Occupation More than half of the participants (54.1%) were QA engineers, but we also had participants from a variety of professions, including SET/SDET, web and mobile application engineers. (2) Experience with Appium More than half (55.7%) of respondents said they had never used Appium, suggesting that many participants were either new to the tool or currently considering adoption. On the other hand, there were also participants with over a year of experience, showing a wide range of maturity in Appium usage. (3) Practical Experience with E2E Testing In terms of E2E testing in general, over half of the respondents were relatively experienced, between 1 and 3 years (27.9%) or more than 5 years (24.6%), confirming that E2E Testing is already being widely used in practice. (4) Topics of Greatest Interest Participants expressed strong interest in the following topics: Case studies and results from adopting Appium for testing Appium usage scenarios, tips, and challenges Integration with CI/CD pipelines and cross-platform (React Native, Flutter, etc.) support Based on these survey results, we'd like to continue to share information that meets your interests and needs in future events. Networking and Future Outlook After the session, participants gathered around enjoyed some pizza for a lively networking session that sparked new ideas and collaborations. Appium Meetup Tokyo will be held regularly, and we're actively looking for speakers and new community members. We'd love to have you join us again next time! For Those Considering Participating Those who want to fully implement automated testing for mobile apps Those who are interested in Appium and seeking specific examples or know-how Engineers and QA personnel interested in CI/CD-based operations Those who want to improve their testing culture by learning from other companies' experiences If any of the above applies to you, we'd love to have you join us at Appium Meetup Tokyo to exchange the latest insights. Future announcements and detailed information will be provided by @AutifyJapan and @KintoTech_Dev . If you have any questions or suggestions, feel free to reach out. We look forward to seeing you at the next "Appium Meetup Tokyo #2"! Archive Distribution https://www.youtube.com/watch?v=zV4WbClGquE
アバター
Hello Hello, I'm Tanachu, a new member who joined the company in October 2024! In this article, we have summarized the impressions of those who joined the company in October and November 2024 immediately after joining the company. I hope this content will be useful for those who are interested in KINTO Technologies, and serve as a reflection for the members who participated in the interview! H.I Self-introduction Nice to meet you! I'm H.I. and I've recently been assigned to the Mobile App Development Group. I've mainly been involved in Android development, but I'm looking forward to growing and contributing together with you all in this new environment. I'm particularly interested not only in development but also in data analysis and service growth, and I hope to contribute as a member of the team. There's still a lot for me to learn, but I'll do my best. I'd really appreciate your continued support along the way! How is your team structured? Our team works in a Scrum framework, and we follow an agile development process. We plan, develop, and review using sprints, striving for continuous improvement in short cycles. What was your first impression of KTC when you joined it? Were there any surprises? Since KINTO Technologies is part of a large corporate group, I expected a more formal atmosphere. But once I joined, I was surprised by how flexible and open it actually is. There are also many study groups, so I'm excited about what's ahead. What is the atmosphere like on-site? Everyone on the team has strong technical skills and a high level of motivation. I think the atmosphere is one where you can easily ask questions or make suggestions. How did you feel about writing a blog post? I'd like to write as much as possible. I want to share what I have learned and my experiences through writing. Questions from Horichan to H.I. Do you have any favorite events within the company (either official or unofficial)? There are a lot of study sessions and opportunities to attend external events. People often share insights about things I didn't know or always wanted to learn, so I think they'll become a great source of my growth in the future. Tell us about your hometown! I'm from an island near Seoul, South Korea. It is rich in nature and known for eel and ginseng production. There are many historical sites such as temples, so it attracts a lot of tourists. Tanachu Self-introduction My name is Tanachu and I joined in October. I am a member of the Cybersecurity Defense Team of the Security and Privacy Group. In my previous role at a security vendor, I was involved in incident response and log analysis related to cybersecurity. In my current team, I am mainly involved in security log monitoring using SIEM and building monitoring systems. How is your team structured? Our group consists of nine members from four different countries, including Japan. We're split into four teams handling incident response related to security and privacy, security guideline assessments, internal cybersecurity frameworks, vulnerability assessments, and SOC. My team has four members, mainly working on vulnerability assessments and SOC. What was your first impression of KTC when you joined it? Were there any surprises? I had heard that KINTO Technologies had people from various countries, but I was still a bit surprised to find that I was the only Japanese on my team. The company is promoting the use of generative AI more strongly than I had expected, and being in an environment where we can easily use it in work was a positive surprise. Beyond just chatting with it, I'm always looking for new ways to use it in my own work. What is the atmosphere like on-site? We have a team lunch once a week with members who can join. It gives the impression that the team really values communication. How did you feel about writing a blog post? It feels a bit strange to be featured in a blog that I used to read before joining the company. Question from H.I to Tanachu Please tell us your own way of relaxing! At the end of the day, I like to unwind by watching the TV Tokyo news program "World Business Satellite." lksx Self-introduction My name is lksx and I joined the company in November. I work as a backend engineer in the DX Development Group. In my previous job, I also worked as a backend engineer. Although I was nervous at first, I'm learning a lot and enjoying my time here so far. Looking forward to working with you. How is your team structured? Our development team currently has eight members. Seven of them work in the Muromachi office, while one joins remotely from the Osaka office. Since the team is spread across locations, we often collaborate online, which makes efficient communication especially important. What was your first impression of KTC when you joined it? Were there any surprises? KINTO Technologies gave me a strong impression of being a startup, and it was very refreshing to see multiple products being developed simultaneously. I was also impressed by several products in the POC stage, which made me feel that the company is willing to take on new challenges. So far, I haven't felt any major surprises, but it's definitely a fast-paced environment, and I'm working on adjusting to it. What is the atmosphere like on-site? Everyone on site is easy to talk to, and the atmosphere is relaxed. Even as a newcomer, I feel comfortable asking for help when I run into questions or issues. How did you feel about writing a blog post? I haven't written a blog post before, so this being my first time, I'm actually kind of excited. Question from Tanachu to lksx What does your typical workday look like? (what do you do right after starting work, what kind of tasks do you mainly do in the afternoon?) Since I've just joined the company, there's still a lot to learn and study. So from 9:30 a.m., I spend about 30 minutes studying to build up my work skills. In the morning, I focus on tasks that require more concentration, and in the afternoon, I tend to work on tasks that are more physically engaging. Kirby Self-introduction I am Kirby and I joined the company in October 2024. After spending six years in Tokyo working at an IT company as a Director and Product Manager, I decided to return to my hometown of Osaka, hoping to make a living there while utilizing my skills. Following a role in planning and managing in-person events, I joined KINTO Technologies. I'm currently working at Osaka Tech Lab as part of the KINTO FACTORY development team in the New Service Development Group. How is your team structured? The KINTO FACTORY development team at KINTO Technologies consists of one manager, two product managers, four frontend engineers, five backend engineers, and one QA member. We develop a website that provides car customization and upgrade services for car owners. At Osaka Tech Lab, development is being carried out by two members, including a backend engineer and myself. What was your first impression of KTC when you joined it? Were there any surprises? Since the teams are spread across Tokyo, Nagoya, and Osaka, I was a bit worried about how communication would work. However, KINTO and KINTO Technologies have a strong "meet in person" culture rooted in the Toyota Group, and we often have opportunities for face-to-face communication, even offline. So, that was a surprise in a good way. (I'm actually writing this blog post from Nagoya!) Osaka Tech Lab members actively engage in cross-project conversations, with strong horizontal connection that inspire many engineers to take on technical challenges. What is the atmosphere like on-site? I am happy to be able to work with members who want to be involved in product and service development and who are passionate about making services better. How did you feel about writing a blog post? I used to read this tech blog a lot before joining, so I'm excited to finally be part of it! Question from lksx to Kirby Is there anything you want to try at KINTO Technologies? Of course, I want to contribute to the growth of the KINTO FACTORY service, but I'd also love to take on the challenge of leading an entire project on my own at Osaka Tech Lab! Jun Self-introduction My name is Jun and I joined the company in November 2024. After gaining experience as a web engineer at a business company for nearly seven years, I decided to join KINTO Technologies in hopes of sharpening my skills in a more challenging environment. How is your team structured? I belong to the backend team in the New Car Subscription Development Group, which has 13 members. There are always multiple projects ongoing in parallel within the team, and for each new project, a small group of about two to four members is selected to drive it forward. What was your first impression of KTC when you joined it? Were there any surprises? I was struck by how many engineers here are highly motivated to learn. In addition to study sessions within the team, company-wide study sessions were also held frequently, with many engineers take part enthusiastically. I used to think engineering was something you studied quietly on your own, so this was a surprise in a good way. What is the atmosphere like on-site? Since everyone joined the company mid-career and brings diverse expertise, there's no rigid way of doing things. This makes our discussions around tech stacks and operational methods very dynamic. I'm inspired every day by the people I work with. How did you feel about writing a blog post? I’ve written tech blogs before, but it's been a while. Writing this made me want to start again. Question from Kirby to Jun Do you have any go-to routines to refresh your mood? I love coffee, so whenever I feel stuck, I take a break and brew a cup to refresh myself. H.I. Self-introduction My name is HI and I joined the company in November. I've worked at several IT startups where I gained experience in marketing, development, and product management. I'm currently working as PdM for KINTO Unlimited, at the Muromachi office in Tokyo. How is your team structured? Our KINTO Unlimited team has three PdMs including myself and around 20 engineers. We've divided roles among the three PdMs. I focus on data analysis and marketing, one handles development, and the other is in charge of UI/UX. I think it's a great balance that allows each of us to play to our strengths. What was your first impression of KTC when you joined it? Were there any surprises? I was a little nervous because the average age here felt a bit higher compared to the companies I've worked for in the past, but everyone is really kind so I can work with ease (really). One thing that surprised me was how big the organization actually is, much bigger than I had expected. Coming from a startup venture company, it was definitely an adjustment to get used to the number of stakeholders and figuring out who does what. What is the atmosphere like on-site? Compared to most companies with products, we have relatively abundant resources, so as long as we have ideas, there's a lot we can try. I also get along well with people from other teams. Recently, near my desk we've had Christmas cake together and even a mini pop-up coffee stand where members bring their beans and grinders. How did you feel about writing a blog post? One day I'd like to write something that isn't a self-introduction! Question from Jun to H.I. Which of your past skills or experiences have been especially useful or valuable at KINTO Technologies? The Python and statistics knowledge I studied as a hobby have been surprisingly useful. My experience in both marketing and development has given me a broad perspective, which I've found very helpful in my role as a PdM. Fúyuán Self-introduction My name is Fúyuán from the Planning and Management Group of the Development Support Division. I joined the company in November 2024. I work at Osaka Tech Lab. I handle back-office operations related to KINTO Technologies' contract-based development projects, including budget planning, invoicing, and payments. How is your team structured? The Planning and Management Group consists of five members, each of whom is responsible for various tasks related to KINTO Technologies' contract development work, such as budgeting, contracts, invoicing/payments, and intellectual property management. Although our team members are based in Tokyo, Nagoya, and Osaka, we stay closely connected through Slack. It feels like we're working side by side despite the physical distance. What was your first impression of KTC when you joined it? Were there any surprises? I came from a more Japanese traditional company, so I was surprised by how fast decisions are made and how flat and open the communication is here. I had a similar impression at my job interview, which attracted me and made me decide to join the company, so it didn't come as a surprise. What is the atmosphere like on-site? There's a strong sense of balance: members are very focused and quiet when working, but when it's time to chat, the conversations are lively and fun. With such a diverse team in terms of nationality, age, and career background, I find myself constantly inspired by them. How did you feel about writing a blog post? I'm usually a Read-Only-Member and don't use social media or blogs in my private life, so I'm a bit nervous... Question from H.I. to Fúyuán What is your morning routine like when working from home? I try to move my body before I start working to get my mind going, so I make a point of doing radio calisthenics. My favorite thing to do is exercise on the balcony while soaking up the sunlight on a nice day! (I just hope allergy season ends soon!) R.O Self-introduction I belong to the Project Promotion Group in the New Service Development Division. I am involved in the KINTO ONE development project as a project manager. In my previous role at an offshore development company, I also worked as a project manager, collaborating with team members in Vietnam on various development projects in the fields of construction, entertainment, and finance. How is your team structured? Including our partner members, we're a team of about 10 people. All of us are project managers, each responsible for a different development project related to KINTO ONE. What was your first impression of KTC when you joined it? Were there any surprises? I had heard that the age group would be a little older than at my previous job and that everyone was hired mid-career, so I was a bit nervous about what the internal atmosphere and relationships would be like. But I found that many people are very smart and open, making it easy to collaborate and ask questions. There are more internal study sessions and participation in external ones than I expected. Many of our members travel to other locations as needed to collaborate offline with members there. I expected most communication to be online, but it's relatively easy to meet in person when needed, which I think is a plus. What is the atmosphere like on-site? Our Project Promotion Group has more of a calm atmosphere rather than a lively one, and both work discussions and casual chats happen in a relaxed manner. We usually communicate through messages using communication tools such as Slack, but we also have many opportunities to visit the development team and directly confirm specifications, so I felt that the atmosphere was conducive to cross-departmental conversations and consultations. I feel that the atmosphere varies from team to team, as some teams with a lot of engineers tend to have more lively conversations around specs and implementation. How did you feel about writing a blog post? I actually enjoy putting my thoughts into words, so I was looking forward to this. I'm happy to have the chance to contribute to the blog that I used to gather information before joining the company. Question from Fúyuán to R.O. How do you keep up with new technologies, products, and services out there? I created a separate account on X (formerly Twitter) to only follow accounts sharing those kinds of updates, so my timeline shows exactly the information I want. I also catch up through casual conversations with friends and acquaintances from other industries (usually over drinks). When I want to get a feel for the excitement surrounding a new service or technology, I attend offline events like conferences. Horichan Self-introduction This is Horichan, who joined the company in October 2024! I'm from Hiroshima, a big Carp baseball fan, and I love sweet potato shochu. I was originally an Android engineer, then transitioned to a PdM role two years ago at my previous job. Now, I’m working as a director on systems designed to solve challenges faced by Toyota dealers, and I'm giving it my all every day! How is your team structured? The DX Solution Group under the Mobility Product Development Division has seven members: one producer, three directors, and three designers, each working on their own assigned projects. What was your first impression of KTC when you joined it? Were there any surprises? My first impression was that it's a company that combines the best of both a large company and a startup company! Things like the rent subsidy and generous paid leave feel like a large company, but the employee-led study sessions (and BeerBash!) and the energy of the CHO All-Hands Meeting definitely bring a startup feel, which I love. If I had to name a surprise, it would probably be the tools. In my previous job, I was in charge of internal system, so I created all of my documents using FigJam or Notion, but here PowerPoint is the main tool. But I'm getting quite used to it! At this rate, I'll aim to become a PowerPoint master.💪 What is the atmosphere like on-site? Everyone in the group is kind and I'm learning in a relaxed environment! Lunch is also fun!🫶 Our project team is made up of professionals from various fields who all work together as one team to solve dealership challenges. How did you feel about writing a blog post? I had written a few blog posts at my previous job, so I was interested in! I'm happy to finally take part!☺️ Question from R.O. to Horichan Is there any company culture or event at KINTO Technologies that you found interesting? I love how many opportunities there are to hear directly from management! During onboarding, we had small group sessions with both President Kotera-san and Vice President Kageyama-san, which was already a surprise. But even after that, there are monthly all-hands meetings, CHO All-Hands Meeting, optional 1-on-1s, and U35 events for younger employees.😳 Kotera-san told us that we could even invite him out for drinks. So a few of us actually did, and we had a great time chatting.🍶 (R.O. and Tanachu were there too!🙌) Closing words Thank you everyone for sharing your thoughts on our company after joining it! There are more and more new members at KINTO Technologies every day! We'll be posting more new-joiner stories from across divisions, so stay tuned! And yes: we're still hiring! KINTO Technologies is looking for new teammates to join us across a variety of divisions and roles. For more details, check it out here !
アバター
This is Nakanishi from the Developer Relations Group. I'm excited to share that we're launching a new community! The mobile app market continues to grow year after year, making fast and high-quality releases more crucial than ever to remain competitive. That's where Appium , a testing tool that works with iOS and Android and feels similar to Selenium, is drawing a lot of attention. That said, implementing and operating Appium comes with its own set of challenges, including OS version differences and the complexity of setting up the environment. To tackle these challenges and share solutions, we've launched a new community: Appium Meetup Tokyo . Reasons for Launching Appium Meetup Tokyo Growing Needs Release cycles for mobile apps are becoming increasingly shorter and manual testing alone is reaching its limits. Appium is a valuable tool for enhancing test automation efficiency, but knowledge about its setup and implementation is still not widely shared. At KINTO Technologies, we've set technical capabilities , development productivity , and release speed as our Core Goals for 2025 , and test automation with Appium plays a central role in achieving all three. With the growing spotlight on development productivity across companies, and as our development speed continues to increase, automated testing is becoming more critical than ever. Lack of information for Japan While English documentation and overseas case studies are on the rise, there's still limited comprehensive Appium information available in Japanese. When putting it into practice, familiar examples and real stories of success and failure can be a huge help. I'm sure many QA engineers and mobile app developers at other companies are facing the same challenges. That’s why we aim to energize the Japanese-speaking community and encourage information sharing to help create an even better automated testing environment. Collaboration with Autify While talking with Autify, a company that provides a mobile app automation testing platform, the idea came up that it would be great to have more opportunities to learn about Appium. As the conversation progressed, we decided to get the community started with small study sessions. Autify uses Appium behind the scenes in their services and has accumulated a wide range of knowledge. Their insights and expertise aren't usually shared publicly, things you wouldn't easily gain just from developing your own apps or in-house automated testing. Personally, I'm really excited about this collaboration. Furthermore, by sharing comprehensive know-how, including not only Appium but also related tools, we aim to build a community that can solve a wider range of problems. We're currently looking for more members to help grow this initiative. Community Activities Regular Study Sessions Introduction to Appium Hands-on sessions using real devices and simulators Explanation of CI/CD pipelines Lightning Talks (LT) and Discussions Case studies from companies that have adopted Appium Sharing issues and know-how unique to the field Sharing the latest updates and useful tips Online Information Sharing Archiving slides and resources Q&As on the community Slack and social media Creating a collection of best practices We're hoping to explore all of these initiatives together with you. First Event Details Date : Thursday, February 20, 2025 19:00-21:30 Location : Autify Tokyo Office (Hybrid event) Main theme : Use case of Appium plugin in Autify (15 min + 5 min Q&A) Guidelines and practices for efficient app test automation (15 min + 5 min Q&A) [Open Call] Lightning Talk 3 (15 min + 5 min Q&A) [Open Call] Lightning Talk 4 (15 min + 5 min Q&A) Check out for details on connpass. https://autifyjapan.connpass.com/event/342867/ If you're interested in presenting or attending, please sign up via the link above! Future Plan Ongoing Events We plan to continue hosting study sessions and workshops to meet the needs of everyone, from beginners and advanced users. Information on Integration with Other Tools We'll also actively cover comparisons and integration examples with other tools like Selenium and Cypress, helping you expand your practical options. Community-Driven Knowledge Sharing As we hold more events, we aim to share a wealth of insights, including stories of success and failure relate to Appium, across the community, contributing to participants' skill development. For Those Considering Participating Those who want to fully implement automated testing for mobile apps Those who are interested in Appium and seeking specific examples or know-how Engineers and QA personnel interested in CI/CD-based operations Those who want to improve their testing culture by learning from other companies' experiences If any of the above applies to you, we'd love to have you join us at Appium Meetup Tokyo to exchange the latest insights. Future announcements and detailed information will be provided by @AutifyJapan and @KintoTech_Dev . If you have any questions or suggestions, feel free to reach out. I'm looking forward to seeing you at Appium Meetup Tokyo!
アバター