TECH PLAY

KINTOテクノロジーズ

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

975

Introduction Hello! I'm Rasel , and today I want to share something that most Android developers overlook - the inlineContent property of Jetpack Compose's Text composable. Recently, while working on the my route app at KINTO Technologies Corporation, we encountered a UI challenge that seemed simple but turned out to be quite complex: displaying colored benefit labels inline with ticket names in our ticket usage history screen. ![Figma design showing inline label and ticket name](/assets/blog/authors/ahsan_rasel/inline-content-compose/FigmaDesign.png =250x) The tickets shown are samples. Please check the app for tickets that are actually on sale. As you can see in the design, we needed a pink labeled text with rounded corners that flows naturally with the ticket name text. Most developers would reach for Row or FlowRow to solve this, but these approaches have significant limitations when dealing with text that needs to wrap and flow naturally. The Problem: When Row and FlowRow Fall Short Consider this UI pattern from our actual design: [With coupon] One-day Pass for all Kagoshima City buses, trams and ferry routes. Where [With coupon] is a pink label with rounded corners that must appear seamlessly inline with the text. Approach 1: Using Row (The Naive Solution) @Composable fun TicketNameWithRow(name: String, benefitLabel: String) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, ) { Box( modifier = Modifier .background( color = MaterialTheme.colors.subPink, // Design system color shape = RoundedCornerShape(2.dp), ) .padding(horizontal = 4.dp), contentAlignment = Alignment.Center, ) { Text( text = benefitLabel, style = MaterialTheme.typography.body3Bold, color = MaterialTheme.colors.onPrimaryHighEmphasis, ) } Text( text = name, style = MaterialTheme.typography.body2Bold, ) } } Result: ![Result of Row approach: label and text misaligned](/assets/blog/authors/ahsan_rasel/inline-content-compose/WithRow.png =250x) Problems with Row: Label doesn't align perfectly with text baseline Inconsistent spacing when text wraps Breaks the natural text flow Approach 2: Using FlowRow (Better, But Still Limited) @Composable fun TicketNameWithFlowRow(name: String, benefitLabel: String) { FlowRow( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, ) { Box( modifier = Modifier .background( color = MaterialTheme.colors.subPink, shape = RoundedCornerShape(2.dp), ) .padding(horizontal = 4.dp), contentAlignment = Alignment.Center, ) { Text( text = benefitLabel, style = MaterialTheme.typography.body3Bold, color = MaterialTheme.colors.onPrimaryHighEmphasis, ) } Text( text = name, style = MaterialTheme.typography.body2Bold, ) } } Result: ![Result of FlowRow approach: label and text still not aligned](/assets/blog/authors/ahsan_rasel/inline-content-compose/WithFlowRow.png =250x) As shown above, the UI still doesn't align perfectly with our design. The label and text do not flow naturally when wrapping. Another Approach: Using AnnotatedString (SpanStyle) Before Jetpack Compose supported true inline composables, many developers tried to achieve similar effects using only AnnotatedString and SpanStyle . This approach uses background color and rounded corners via SpanStyle to style part of the text as a label. Approach 3: AnnotatedString with SpanStyle This method is limited compared to inlineContent , but can be useful for simple cases where you only need background color and text styling (not custom composables or padding). @Composable fun TicketNameWithAnnotatedString(name: String, benefitLabel: String) { val cornerRadius = with(LocalDensity.current) { 2.dp.toPx() } val drawStyle = Stroke(pathEffect = PathEffect.cornerPathEffect(cornerRadius)) val nameStyle = MaterialTheme.typography.body2Bold.toSpanStyle() val benefitStyle = MaterialTheme.typography.body3Bold.copy( color = MaterialTheme.colors.onPrimaryHighEmphasis, background = MaterialTheme.colors.subPink, drawStyle = drawStyle, ).toSpanStyle() val annotatedString = remember(name, benefitLabel) { buildAnnotatedString { withStyle(style = benefitStyle) { append(" $benefitLabel ") // Add spaces for padding effect } append(" ") withStyle(style = nameStyle) { append(name) } } } Text(text = annotatedString) } Result: ![Result of AnnotatedString approach: label with background color, no rounded corners](/assets/blog/authors/ahsan_rasel/inline-content-compose/WithAnnotatedString.png =250x) Limitations: Rounded corners are not achieved properly while using proper value from design (background is mostly a rectangle, and needs to adjust value to make it rounded, still proper rounding is not achieved) No custom composable or icon support Padding is simulated with spaces, not true padding Baseline alignment is good, but label may not look as polished as with inlineContent Text styling also gets changed from design When to use: When you only need a colored background and simple text styling When you want a dependency-free, simple solution for basic badges The Solution: InlineContent - The Proper Way InlineContent allows you to embed custom composable directly within text, treating them as characters in the text flow. Here's how we implemented it in the my route app: Step 1: Understanding the Production Implementation Below is a simplified version of our production code, showing how to use inlineContent to embed a custom label inside text: @Composable private fun TicketNameWithBenefit( name: String, benefitLabel: String, ) { val nameStyle = MaterialTheme.typography.body2Bold.toSpanStyle() val annotatedString = remember(name, benefitLabel) { buildAnnotatedString { appendInlineContent(benefitLabel) // Use label text as unique key append(" ") withStyle(style = nameStyle) { append(name) } } } // ... (inline content implementation) Text( text = annotatedString, inlineContent = inlineContent, ) } Step 2: Dynamic Width Calculation (The Critical Part) Calculating the exact width for the label background is essential for a seamless look. Here is how we do it: The most challenging aspect is calculating the exact width needed for the label background: val density = LocalDensity.current val textMeasurer = rememberTextMeasurer() val horizontalPadding = 4.dp val benefitLabelBgWidthSp = remember(benefitLabel) { val textLayoutMeasure = textMeasurer.measure( text = benefitLabel, style = benefitStyle, ) with(density) { (textLayoutMeasure.size.width.toDp() + (horizontalPadding * 2)).toSp() } } Why this is complex: Text Measurement : We need to measure the text before rendering Unit Conversion : Convert between pixels, dp, and sp correctly Padding Calculation : Include padding in the total width Density Awareness : Handle different screen densities Step 3: Creating the Inline Content Mapping Now, we map the label to a composable using InlineTextContent , ensuring it aligns and sizes perfectly: val inlineContent = remember(benefitLabel, benefitLabelBgWidthSp) { mapOf( benefitLabel to InlineTextContent( placeholder = Placeholder( width = benefitLabelBgWidthSp, height = benefitStyle.lineHeight, placeholderVerticalAlign = PlaceholderVerticalAlign.Center, ), ) { Box( modifier = Modifier .background( color = MaterialTheme.colors.subPink, shape = RoundedCornerShape(2.dp) ) .padding(horizontal = horizontalPadding), contentAlignment = Alignment.Center, ) { Text( text = benefitLabel, style = benefitStyle, color = MaterialTheme.colors.onPrimaryHighEmphasis, ) } } ) } Key implementation details: placeholderVerticalAlign = PlaceholderVerticalAlign.Center ensures perfect alignment height = benefitStyle.lineHeight matches the text height exactly Dynamic width calculation ensures perfect fit for any label text The Complete Production Implementation Here's our actual implementation from the my route app: @Composable private fun TicketNameWithBenefit( name: String, benefitLabel: String, ) { val nameStyle = MaterialTheme.typography.body2Bold.toSpanStyle() val benefitStyle = MaterialTheme.typography.body3Bold val annotatedString = remember(name, benefitLabel) { buildAnnotatedString { appendInlineContent(benefitLabel) append(" ") withStyle(style = nameStyle) { append(name) } } } val density = LocalDensity.current val textMeasurer = rememberTextMeasurer() val horizontalPadding = 4.dp val benefitLabelBgWidthSp = remember(benefitLabel) { val textLayoutMeasure = textMeasurer.measure(text = benefitLabel, style = benefitStyle) with(density) { (textLayoutMeasure.size.width.toDp() + (horizontalPadding * 2)).toSp() } } val inlineContent = remember(benefitLabel, benefitLabelBgWidthSp) { mapOf( benefitLabel to InlineTextContent( placeholder = Placeholder( width = benefitLabelBgWidthSp, height = benefitStyle.lineHeight, placeholderVerticalAlign = PlaceholderVerticalAlign.Center, ), ) { Box( modifier = Modifier .background( color = MaterialTheme.colors.subPink, shape = RoundedCornerShape(2.dp), ) .padding(horizontal = horizontalPadding), contentAlignment = Alignment.Center, ) { Text( text = benefitLabel, style = benefitStyle, color = MaterialTheme.colors.onPrimaryHighEmphasis, ) } }, ) } Text( text = annotatedString, inlineContent = inlineContent, ) } Result: ![Result of inlineContent approach: label and text perfectly aligned](/assets/blog/authors/ahsan_rasel/inline-content-compose/WithInlineContent.png =250x) As you can see, this implementation perfectly aligns with our design goal. Why We Switched from External Library When we couldn't achieve our design using Row, FlowRow and even AnnotatedString approach, we tried using an third party library: val extendedSpans = remember { ExtendedSpans( RoundedCornerSpanPainter(…), ) } Text( modifier = Modifier.drawBehind(extendedSpans), text = remember(text) { extendedSpans.extend(text) }, onTextLayout = { result -> extendedSpans.onTextLayout(result) } ) By using a third-party library, we finally achieved our design goal. But we don't want to ship another library with our app just for this single Text UI item and reconsidered other approaches. Problems we encountered: Additional dependency for a simple feature Less control over styling and behavior Potential compatibility issues with future Compose versions The native solution proved to be better: No external dependencies Full control over styling and behavior Better performance Future-proof with Compose updates Advantages of InlineContent Approach Natural Text Flow : Labels wrap with text naturally, maintaining proper line breaks Perfect Alignment : Consistent baseline alignment with surrounding text Flexible Styling : Full support for complex text formatting (bold, colors, sizes) Dynamic Sizing : Adapts to content automatically while maintaining design consistency Performance : Efficient rendering as part of the text layout engine Accessibility : Screen readers handle it as part of the text flow Common Pitfalls and Solutions Pitfall 1: Hardcoded Dimensions ❌ Wrong: Placeholder(width = 60.sp, height = 16.sp) // Fixed dimensions ✅ Correct: Placeholder( width = calculatedWidth, // Dynamic based on content height = textStyle.lineHeight, // Match text height ) Pitfall 2: Incorrect Unit Conversion ❌ Wrong: textLayout.size.width.toSp() // Direct conversion loses density information ✅ Correct: with(density) { textLayout.size.width.toDp().toSp() // Proper density-aware conversion } Pitfall 3: Missing Performance Optimization ❌ Wrong: // Recalculated on every composition val inlineContent = mapOf(...) val annotatedString = buildAnnotatedString { ... } ✅ Correct: val inlineContent = remember(dependencies) { mapOf(...) } val annotatedString = remember(dependencies) { buildAnnotatedString { ... } } Use Cases Beyond Benefit Labels This technique works excellently for: Status badges in lists Rating stars within reviews Currency symbols with special styling Icon indicators in text Highlighted keywords in search results Performance Considerations The remember usage is crucial for performance: // Expensive operations cached properly val benefitLabelBgWidthSp = remember(benefitLabel) { /* text measurement */ } val inlineContent = remember(benefitLabel, width) { /* composable creation */ } val annotatedString = remember(name, benefitLabel) { /* string building */ } This ensures calculations only happen when dependencies change, not on every composition. Conclusion InlineContent is a powerful but underutilized feature of Jetpack Compose. When you need to embed custom UI within text flow, it's the superior solution compared to layout-based approaches like Row or FlowRow . The dynamic width calculation technique we've implemented solves the common problem of perfectly fitting background elements to text content. While the implementation requires understanding text measurement and unit conversion, the result is a professional, performant UI that scales well across different devices and text sizes. Our experience transitioning from an external library to this native solution proved that sometimes the built-in tools, when used correctly, provide the best balance of performance, maintainability, and design flexibility. Next time you encounter inline UI challenges, remember: inlineContent is your friend! Key Takeaways: Use InlineContent for true inline UI elements that need to flow with text Implement dynamic width calculation using TextMeasurer for perfect fitting Optimize with remember for performance, especially text measurements Prefer native solutions over external libraries when built-in APIs suffice Pay attention to unit conversion and density handling Happy coding!
はじめに こんにちは!KINTOテクノロジーズQAグループで主にWebフロントエンド案件を担当しているoshimaです。 弊社は全社を挙げてAI利用を推進していますが、その波に乗って先月生成AIコーディング企画”Vibe Coding Week”が行われました。この企画で私がチャレンジした内容についてご紹介いたします。 Vibe Coding Week企画そのものの説明は他の記事をご参照いただければと思います。 背景 私自身、通常業務でコーディングをほとんど行うことはありません。一方、私の担当する案件については一つ一つのリリースサイクルが速いことと、同じ期間に複数の案件が重複することで、効率の良いテストが求められていました。 となればテスト自動化が一つのカギになります。実際グループではPlaywrightを用いての自動化が推進されてきました。が、私自身は残念ながら「コードの書けない人」です。これまでは出来る同僚に依頼するなどの方法でしのいできましたが、いつまでも当てにし続けられない(なかなか手が空かない)という状態が続いていました。 救世主!? 自動化出来れば効率アップできそうな案件関連のネタは持っている。ただ、自身のスキルではどうしようもなかった。そんなもどかしい状態を打ち砕いてくれたのが生成AIです。 やりたいテスト(条件と期待値、制約条件など)がはっきりしていて、AIに正しく伝えることができれば、お望みのコードを生成してくれる。これならコーディングできない自分でも自動化に対して立ち向かっていける。業務の効率化のみならず、今まで自分が越えられなかった壁を越えてくれる(かもしれない)、救世主のような存在がAIでした。 チャレンジ企画との親和性 日本語のプロンプトでPlaywrightによる自動テストコードを生成していこうという取り組みは、先述のVibe Coding Week企画関係なく自然発生的なものでした。なので、企画発表があった時点で、企画意図に沿った内容の報告ができるのではないか?という感触を持っていました。 適用案件の特徴 通常業務での効率化の対象とした案件、そしてチャレンジ企画で取り組んだ案件が、社内では「静的資材」案件と呼ばれているものです。主にはKINTO ONEのサービスで取り扱う車種の仕様をお客様に紹介するためのページや契約内容の案内ページなどの確認を行う案件です。 例: トヨタ ヤリスの紹介ページ 特徴 車種紹介のページになると、KINTOで取り扱う車種が増えるごとに案件が成立しますが、ページレイアウトや項目は全車種共通。異なるのは画像と価格などのため、確認する内容は車種が変われども同じという案件の特徴がありました。 通常のテストでの作業内容 通常のテストで実施していた内容は以下となります。 全体デザインがFigmaで作成されたデザインと比較して差異がなく、またレイアウトの崩れがないこと 表示されている価格・サイズ・燃費などの各種データが指定ファイルと同じであること 画面内のボタンやリンクの遷移先が仕様通りであること これらの確認を今までは目視確認で行ってきました。原始的なやり方ですが、ある程度不具合指摘もできていたのですが、数が重なってくるとこれでは厳しくなってきます。上記の確認3項目については、うまくやれば自動で済みそうな内容です。 幸い試験時にはFigmaでの画面デザイン、価格表や燃費・サイズの記載された諸元表の指定ファイルは提示されます。材料はあります。なので、テスト対象ページの記載内容と自動で比較は特に問題なくできるのではないかという仮説を立て、チャレンジしてみました。 チャレンジ企画での取り組み 全体デザインの自動比較 デザインの比較については、次の手順で取り組みました。 テスト対象の該当Figmaページからコンポーネント情報を抽出してJSONに出力を試みる 全部だと情報量が多く読み込みに失敗 抽出する情報を位置情報に絞って相対位置比較でレイアウト崩れ確認に移行 レイヤーやID情報などが複雑で、ほしい情報を整理するのに苦労 実画面情報との解像度の差もあって、単純な比較でエラー頻発 残念ながらこのチャレンジ期間ではデザイン周りはうまくいきませんでした。 価格などの表示情報の自動比較 こちらについては、次の手順で取り組みました テストページの表示情報を抽出してJSONに出力を試みる 通常テキストは余裕でしたが表形式は項目名指定で工夫が必要でした 元データ(Excel)から情報抽出して同様にJSON出力 比較をしたいので「基本性能・主要装備」と「プラン別ご利用料イメージ」部分を抽出 結果的にうまくいきましたが、実はいろいろ試行錯誤し、結局はセル番号指定でのベタな指令に落ち着きました。 作成された2つのJSONを比較 ここまでくると問題なく実行完了 テキスト情報は比較的スムーズにうまくいきました。 リンク遷移先の確認 こちらは全部終わらなかったのですが、時間かければできそうな実感がありました。 ボタンに設定されたリンク先の取得は問題なし 目視の方が速そうなので比較の自動化までは実施せず チャレンジ企画の成果 ポイントは、単純に情報抽出してくださいではなく、テストコードをタイプスクリプト形式で実装し、Playwrightで実行したことです。このコードを生成しておくことで、他の車種や元データファイルが変わっても少しの改変で対応できるようになりました。 今まで目視確認で行っていたテスト内容を一部でも自動化出来たことは一つの成果ですが、できていない箇所や、より効率的にテストが進められる改善の余地は、残念ながらまだまだ多いです。 とはいえ、チャレンジ企画の短期間である程度できたことも確かです。地道に継続することで、出来てない部分を埋めていけると思います。 未来への妄想 -まとめにかえて- 自動化でできることはとことん自動化ツールでの確認に任せて、人間でしか確認できない箇所を追求する。効率化で時間的な余裕を生み、結果として人力でのテストの質の向上まで図ることができれば理想的です。 現段階ではまだまだ妄想です。ただ、コーディングできない人間でもある程度の自動化ができる時代になった今、妄想の実現は決して遠くない未来にあるのではという期待感も持っています。 チャレンジ企画で対応した案件以外での取り組みも含め、少しずつでも歩みを進めていこうと思います。
こんにちは、KINTOテクノロジーズ大阪拠点「Osaka Tech Lab」所属の中村です。 KINTOテクノロジーズは、トヨタグループの内製開発部隊として、車のサブスクリプションサービス「KINTO」をはじめとしたさまざまなモビリティサービスを開発を行っています。 私たちの拠点は2025年6月に梅田のノースゲートビルディングへ移転し、現在は月に一度のペースで社外向けの勉強会「CO-LAB Tech Night」を開催しています。1つのテーマを設定し、開発・デザイン・データなど、職種の垣根を越えたメンバーが日々の取り組みや気づきを共有し合う勉強会となっています。 今回は、これまでの歩みと、次回Vol.4「生成AI × クリエイティブ」についてご紹介します。 CO-LAB Tech Nightの成り立ち もともと、Osaka Tech Labには、 社外の勉強会やイベントで登壇経験のあるメンバーが何人かいました。ただ、以前のオフィスは手狭で、社内でイベントを開けるようなスペースがありませんでした。「大阪から発信する場をつくりたいね」という話は出ていたものの、なかなか形にできずにいました。 2025年6月末の移転をきっかけに、念願のイベントスペースが整い、 自分たちでもコミュニティを育てていけるようになりました。 関西では、 LINEヤフー株式会社様 をはじめ、 さくらインターネット株式会社様 、 株式会社マネーフォワード様 、 Sansan株式会社様 など、さまざまな企業がコミュニティ活動を展開しています。 そうした盛り上がりを肌で感じながら、私たちももっと関西の企業やコミュニティの方々とコラボレーションして、大阪からIT業界を盛り上げていきたい。 そんな思いから始まったのが「CO-LAB Tech Night」です。 Vol.1〜3の歩み Vol.1|クラウド開発のリアルを共有 初回はクラウド開発をテーマに、Osaka Tech Labメンバーが登壇。アーキテクチャ設計やIaC化の試行錯誤を中心に、内製開発の現場感をそのまま伝えました。 Vol.2|クラウド活用に役立つセキュリティ実践例 2回目はクラウドセキュリティを軸に、AWSやAzureなど各チームの知見を共有。「攻めと守りをどう両立するか」という実務的な問いを中心に議論しました。 Vol.3|QAエンジニアの役割を再考する 3回目はQA(品質保証)をテーマに開催。生成AI時代を迎えた自動化・テスト設計など、常識が変わりつつあるテストの在り方を中心に語り合いました。 見えてきた課題とこれから CO-LAB Tech Nightを続ける中で、関西ではまだKINTOテクノロジーズのことがあまり知られていないと実感することが多くあります。「KINTOテクノロジーズって大阪にもあるんですね」とお声がけいただくことも多く、Osaka Tech Labの認知は、まだまだこれからだと感じています。 現状では、登壇者や運営メンバーの呼びかけで参加者が集まることが多く、どうしても社員個人のつながりや発信力に支えられている部分が大きいのが実情です。一方で、CO-LAB Tech Nightやその他のイベントへの出展などを通じて「大阪にもこういう会社があるんだ」と知っていただく機会は確実に増えてきました。 もともと他拠点に比べても交流が多く、メンバー間の距離も近い環境ですが、改めて「こういう視点で仕事をしていたんだ」と気づく場面が増え、お互いの理解をより深めるきっかけにもなっています。 メンバーの想いや行動をきっかけに始まったこの活動を、組織としての発信やコラボレーションの仕組みとして根付かせていきたい。そして関西の企業や、関西で働く方々とともに、大阪からIT業界を盛り上げていきたいと考えています。 次回:生成AI × クリエイティブの可能性と現在地 次回のVol.4は、デザイナーによるTikTokエフェクト制作、イラストワークフロー、画像生成比較、AIを使ったキャラクター表現など、実際にトヨタグループの内製開発で活用している事例を紹介します。 📅 2025年10月24日(金)19:00〜21:30 📍 KINTO テクノロジーズ Osaka Tech Lab (大阪駅直結・ノースゲートビルディング) ▼申込・詳細はこちらのconnpassページをご確認ください。 https://kinto-technologies.connpass.com/event/369528/ https://www.youtube.com/watch?v=v1fFP5VZEF0 おわりに CO-LAB Tech Nightは、「話してみたい」「聞いてみたい」という声から企画が生まれた、Osaka Tech Labのカジュアルな勉強会シリーズです。社内外の人がつながり、学び合う場として続けていくことで、大阪発のITコミュニティとして、ゆるやかに広がっていけたらと思います。 関西で活動するエンジニア、デザイナー、PdMのみなさん、ぜひ気軽に遊びにきてください! ▼ KINTO テクノロジーズ Osaka Tech Labのコンセプトや働く人の様子 https://www.kinto-technologies.com/company/osakatechlab/ ▼ Osaka Tech Labでは、エンジニア・デザイナー・PdMなど複数職種で仲間を募集中です! https://www.wantedly.com/projects/2069536
Hello, I'm Nakamura from KINTO Technologies' Osaka Tech Lab. KINTO Technologies is Toyota Group's in-house development team, creating various mobility services including the car subscription service "KINTO." Our office relocated to North Gate Building in Umeda in June 2025, and we now host a monthly external study session called CO-LAB Tech Night. We set a theme for each session where members from various roles including development, design, data and more, share their daily efforts and insights across professional boundaries. In this article, I'll introduce our journey so far and our upcoming Vol.4 session: Generative AI × Creativity. The Origins of CO-LAB Tech Night Osaka Tech Lab has always had several members with experience presenting at external study sessions and events. However, our previous office was small, and we simply didn't have the space to accommodate events. While we talked about creating a platform to share from Osaka, we couldn't quite make it happen. The relocation at the end of June 2025 finally gave us the event space we'd been hoping for, enabling us to nurture our own community. In Kansai, companies like LY Corporation , SAKURA internet Inc. , Money Forward, Inc. , and Sansan, Inc. are actively building communities. Experiencing this vibrant ecosystem firsthand, we wanted to collaborate more with companies and communities in Kansai and energize the IT industry from Osaka. That's how CO-LAB Tech Night was born. Journey Through Vol.1–3 Vol.1 | Sharing the Reality of Cloud Development Our first session focused on cloud development, with Osaka Tech Lab members presenting. We shared the real-world challenges of architecture design and IaC implementation from our in-house development perspective. Vol.2 | Practical Security Examples for Cloud Utilization The second session centered on cloud security, sharing insights from teams using AWS, Azure, and other platforms. We discussed the practical question of how to balance innovation and security. Vol.3 | Reconsidering the Role of QA Engineers Our third session explored QA (Quality Assurance). We discussed evolving testing practices in the generative AI era, including automation and test design as conventional wisdom shifts. Challenges and the Road Ahead Through continuing CO-LAB Tech Night, we've realized that KINTO Technologies isn't wildely recognized in Kansai. We often hear, "Oh, KINTO Technologies has an Osaka office?" indicating that Osaka Tech Lab's recognition is still developing. Currently, participants often join through connections with speakers or organizers, meaning our reach still heavily depends on individual employees' networks and influence. However, through CO-LAB Tech Night and participation in other events, we're steadily increasing opportunities for people to learn about our company's presence in Osaka. While we've always had strong cross-team collaboration and close relationships compared to other offices, these sessions have created new moments of realization, like "I didn't know you approached work from that perspective," deepening our mutual understanding. We want to transform this activity, which began with members' passion and initiative, into an organizational mechanism for outreach and collaboration. And together with companies and professionals working in Kansai, we want to energize the IT industry from Osaka. Next: The Potential and Current State of Generative AI × Creative Vol.4 will showcase real-world examples from Toyota Group's in-house development, including TikTok effect creation by designers, illustration workflows, image generation comparisons, and AI-powered character expression. 📅 October 24, 2025 (Fri) 19:00–21:30 📍 KINTO Technologies Osaka Tech Lab (directly connected to Osaka Station, North Gate Building) ▼ For registration and details, please check our Connpass page: https://kinto-technologies.connpass.com/event/369528/ https://www.youtube.com/watch?v=v1fFP5VZEF0 Closing CO-LAB Tech Night is a casual study session series born from the desire to talk and listen. We hope it will organically grow as an Osaka-based IT community where people inside and outside the company connect and learn together. Engineers, designers, and PdMs active in Kansai, please feel free to drop by! ▼ Learn about KINTO Technologies Osaka Tech Lab's concept and people: https://dev-www.kinto-technologies.com/company/osakatechlab/ ▼ Osaka Tech Lab is hiring engineers, designers, PdMs, and other roles! https://www.wantedly.com/projects/2069536
はじめに QAグループのMobileチームのokapiです。 「1週間、生成AIを使ってどこまで開発生産性を上げられるか?」そんなチャレンジ精神に火がついた社内イベント「Vibe Coding Week(バイブコーディングウィーク)」に、QAグループも参戦しましたので、執筆させていただきます。 なお、この記事のカバー画像もAIで作成してみました。 Vibe Codingとは? Vibe Codingとは、生成AIと人間のエンジニアが協働して行う新しい開発スタイルです。 AIが「コード生成」や「調査」といった重い作業を引き受け、人間は「品質の検証・改善」に集中することで、高品質かつ効率的な開発を行えます。 「AIの力を最大限に引き出しながら、人間にしかできない価値を提供する」——それがVibe Coding Weekです。この1週間で、AI活用の限界と可能性を体験し、チーム内のAI活用に対する理解を深めます。 QAは何をやるのか? 「開発生産性って、QAに関係あるの?」そう思われた方もいるかもしれません。 QAグループでは日々、「自動化・業務改善・AI活用」に積極的に取り組んでいます。 日々の業務で培ったノウハウを武器に、このVibe Coding Weekに挑戦することになりました。 私はQAグループの代表として参戦。 イベント開始の1ヶ月前から週次ミーティングを重ね、「どうすれば成果を出せるのか?」を徹底的に議論しました。 その結果、QA業務を楽にする「3つの自動化チャレンジ」を実施することに決定しました! 取り組んだ3つの自動化チャレンジ 中古車データ作成の自動化と、画面仕様と実画面比較の自動化については、担当メンバーが詳細記事を執筆予定です。ぜひ公開を楽しみにお待ちください。 チャレンジ 概要 効果 1. Appiumライブラリのメジャーバージョンアップ QA Mobileチームのテスト自動化で利用しているAppiumのライブラリを、メジャーバージョン2つ分(java-client: 7.6.0 → 9.0.0)にアップデート。AIを活用してリリースノート調査、修正タスク化、実装を高速化し、互換性のある周辺ライブラリも同時更新。 メンテナンス性の向上。今後自動化予定のAppiumで行う画面比較に対応したaShotライブラリ(ピクセルパーフェクト比較)が使用可能に! https://github.com/pazone/ashot 2. 中古車データ作成の自動化 QA Webチームのテストで必要な中古車のデータ作成をPlaywrightで自動化 従来200台作成に約2ヶ月かかっていたが、自動化により工数を削減。これまで手作業で行っていた面倒な作業から解放! 3. 画面仕様と実画面比較の自動化 QA Webチームのテストで手動で行っていたExcel仕様書と実画面の比較をVS Codeで自動化。仕様書から文字抽出→差分判定→差分箇所表示を自動化 テスト実施の確認時間を削減。目視で探していた差分が一瞬でわかるように! 各メンバーの感想 メンバー 感想 大島さん これまで地道に取り組んできたことを発表でき、さらに実案件への展開の可能性も見えてきたと感じました。通常業務の中でAIを活用できたのも良かったです。 呂さん 今回の成果物は、今後の案件業務に活用できそうです。 小林さん ClaudeCode、Copilot、Devinをそれぞれ使ってみて、性能の違いをある程度把握できました。どれを使う場合でも、調査や実装のスピードが人力よりも明らかに速い。 パンヌさん GitHub Copilotを使ってMCPのサーバー構築を行い、Figma関連の理解も深まり、すべての作業を完了できました。とても満足しています。 岡 皆さんが事前準備からしっかり取り組んでいたおかげで、3チームとも成果を出せていたので、良いイベントでした。 成果と学び AI活用により、調査・修正作業のスピードと精度が向上。 自動化によって工数を削減し、人的リソースをより重要な品質保証やテスト設計に集中できるようになった。 短期間で普段着手できなかった改善を実施でき、AI活用文化の形成にもつながりました。 おわりに 今回のVibe Coding Weekでは、QA業務の自動化と効率化に向けた3つのチャレンジを実施し、短期間で大きな成果を得ることができました。 事前準備に1ヶ月かけた甲斐があり、イベント期間中は全力疾走。 この経験で得たノウハウは、今後のQA業務に大きく活かされるはずです。 今後も「自動化・業務改善・AI活用」を継続的に推進するとともに、引き続き、色んなチャレンジに挑戦していきます。
Introduction I'm okapi from the Mobile team in the QA Group. "How much can we boost development productivity using generative AI in one week?" With that challenging spirit ignited, the QA Group also joined the internal event Vibe Coding Week, and I'm writing this article to share our experience. By the way, the cover image for this article was also created using AI. What is Vibe Coding? Vibe Coding is a new development style where human engineers collaborate with generative AI. AI takes on heavy tasks like code generation and research, while humans focus on quality verification and improvement, which enables high-quality and efficient development. Maximizing the power of AI while delivering value that only humans can provide—that's what the Vibe Coding Week is all about. During this week, we experience the limits and potential of the AI use to deepen the team's understanding of AI adoption. What Does QA Do? Does development productivity really relate to QA? Some of you might have thought such question. The QA Group actively works on automation, workflow improvement, and AI utilization every day. Obtaining the know-how cultivated through daily work, we decided to take on this Vibe Coding Week challenge. I participated as a representative of the QA Group. Starting one month before the event, we held weekly meetings and thoroughly discussed how we can deliver results. As a result, we decided to implement three automation tasks to make QA work easier! The 3 Automation Tasks We Tackled One of the QA team members will write detailed articles about the automation to create used vehicle data and to compare a screen design on specification with an actually developed screen. Please look forward to the article release. Challenge Overview Effect 1. Major version upgrade of Appium Library Updated the Appium library used for test automation by the QA Mobile team by two major versions (java-client: 7.6.0 → 9.0.0). Leveraged AI to accelerate release note research, modification task creation, and implementation, while updating compatible peripheral libraries simultaneously. Improved maintainability. Now able to use the aShot library (pixel-perfect comparison) compatible with screen comparison planned for future Appium automation! https://github.com/pazone/ashot 2. Used vehicle data creation automation Automated the creation of used vehicle data needed for testing by the QA Web Team using Playwright Previously, creating data for 200 vehicles took about 2 months, but automation reduced the team's workload. Freed from the tedious manual task! 3. Comparison automation between screen design on specification and actual screen Automated the comparison between screen design on specification in Excel and actually developed screen with VS Code that used to be done manually by the QA Web Team. Automated a process from extracting texts from specifications, identifying differences between design on specification and actual screen, and displaying where the differences are identified. Reduced verification time during the test phase. Differences that used to be checked visually can now be identified instantly! Team Members' Impressions Member Impression Oshima-san I was able to present what we had been constantly working on and expect it to be applied for actual projects. It was also good to be able to utilize AI in regular work. Lu-san The deliverables from this time seem useful for future project work. Kobayashi-san By using ClaudeCode, Copilot, and Devin respectively, I was able to grasp their performance differences to some extent. Whichever I used, the speed of research and implementation is definitely faster than manual work. Pann Nu-san I used GitHub Copilot to build an MCP server, which brought me an opportunity to deepen my understanding of Figma-related topics and complete all the work. I'm very satisfied. Oka Thanks to everyone's thorough preparation from the start, all three teams were able to deliver results, so it was a great event. Results and Learnings AI utilization increased the speed and accuracy of research and modification tasks. Automation reduced workload, allowing humans to focus on more important quality assurance and test design. We were able to make improvements that we couldn't normally tackle in a short amount of time. This contributed to forming a corporate culture where we automate routine tasks by leveraging AI. Conclusion In this Vibe Coding Week, we implemented three tasks aimed at automating and improving efficiency of QA work, achieving significant results in a short term. The one month of preparation paid off, and we sprinted at full speed during the event. The know-how gained from this experience should greatly benefit our future QA work. We will continue to promote automation, workflow improvement, and AI utilization continuously, and keep addressing various challenging tasks.
Tips for Safe AI Image Editing—Learning from Azure OpenAI Service Introduction As of October 2025, AI-related news is reported daily, covering topics like how amazing the new model is or what it can do now. Since technological progress is remarkable, complex images can currently be edited easily with AI tools. While taking advantage of them, we should not forget the perspective of how to use them safely. We are often captivated by performance aspects such as what amazing images can be created or how naturally they can be edited, but it is equally important to consider whether generated images could potentially harm others. Learning Responsible AI from Enterprise Services Microsoft's Azure OpenAI Service (AOAI) is a very helpful reference from this perspective. AOAI is designed for enterprises and government agencies. While it uses the same technology as OpenAI, it incorporates more stringently ethical standards (guardrails) based on the principles of Responsible AI. Of course, OpenAI also upholds similar principles, but AOAI is operated more carefully. Why Does AOAI Have Stricter Policies? For example, when a corporation uses OpenAI's API, personal identity verification of the administrator is required to prevent unauthorized use. This is an approach clarifying the responsibility of the individuals who operate the API. On the other hand, AOAI is a corporate cloud service, and its use is authorized under Azure contracts and authentication infrastructure (e.g., Microsoft Entra ID). It is not realistic for each employee to submit identification documents to use a service within a company. AOAI takes this into consideration and eliminates the need for individual authentication by trusting the corporation . In short, trust is placed in the organization. With this approach, AOAI, as an enterprise platform, has set up particularly stringent restrictions (guardrails) on its use. Examples of Operations Blocked by AOAI So, what specific restrictions are in place? Based on my testing experience, the following operations were blocked by AOAI. (For testing, I used the gpt-image-1 model.) Instructions to edit images of minors (including context-based judgments, like someone in a school uniform) Including insulting or discriminatory words in prompts Instructions to swap the face of a specific individual These restrictions show that AOAI focuses on at least the following three points: AOAI's Three Key Areas of Focus Protecting minors who are in socially vulnerable positions Protecting human dignity from verbal violence such as discrimination and insults Preventing the spread of misinformation through deepfakes and similar technologies To put it simply, this isn't a wall to restrict freedom but a framework to protect others and society. Such framework is extremely helpful for avoiding unintentionally hurting someone and for not accidentally becoming a perpetrator. Transparency in the AI Era: Content Credentials Recently, as image generation and editing have become easily accessible to individuals, the risk of unintentionally hurting others has also increased. However, to address such challenges, images generated or edited by AI tools often have Content Credentials , metadata embedded in the images. This is a new system designed to ensure transparency in AI generation. It records information such as: When it was created ; What tools were used ; and Who edited it . It is like a digital version of a nutrition facts label . The system enables the detection of content tampering and incidental modification. In other words, it allows people who properly use AI to publish content with confidence and authenticity. Summary Instead of getting swept away by convenience, take a moment to ask yourself: "Could this content cause harm to others?" Building up this kind of awareness is the first step toward using AI safely. I believe that, ultimately, this protects both you and your organization.
Azure OpenAI Serviceに学ぶ、安全なAI画像編集のヒント はじめに 2025年10月現在、「新しいモデルはどのくらいすごい」「こんなことができる」といったAI関連ニュースが毎日のように流れてきます。 技術の進歩は目覚ましく、今や複雑な画像編集も、AIを使えば比較的簡単に行うことができるようになりました。こうした恩恵を受けられる一方で、忘れてはならないのが「AIをどのように安全に使うか」という視点です。 私たちはつい「どんなすごい画像が作れるか」「どれくらい自然に編集できるか」といった性能面に目を奪われがちですが、その一方で、生成された画像が人を傷つける可能性があるかどうかという観点を持つことも大切です。 企業向けサービスに学ぶ「責任あるAI」 この点で非常に参考になるのが、MicrosoftのAzure OpenAI Service(AOAI)です。 AOAIは企業や行政機関向けに設計されており、OpenAIと同じ技術を用いながらも「責任あるAI(Responsible AI)」の原則に基づき、より厳格な倫理基準(ガードレール)が組み込まれています。 もちろんOpenAIも同様の原則を掲げていますが、AOAIのほうがより慎重に運用されています。 なぜAOAIはより慎重なのか? たとえば、OpenAIのAPIを法人で利用する場合、不正利用を防ぐために管理者 個人の身分証明による本人確認 が求められます。これは、APIを操作する「個人」の責任を明確にするアプローチです。 一方、AOAIは法人向けクラウドサービスであり、Azureの契約と認証基盤(Microsoft Entra IDなど)に基づいて利用が許可されます。企業でサービスを利用する際に従業員一人ひとりが身分証明書を提出するのは、現実的ではありません。AOAIはそこを考慮し、 法人を信頼することで個人認証を不要に しています。いわば契約した「法人」を信頼するアプローチです。 その上でAOAIでは企業向けプラットフォームという立場から、特に慎重な利用制限(ガードレール)を設けています。 AOAIでブロックされる操作例 では、具体的にどのような制限があるのでしょうか。 筆者が実際に検証したところ、AOAIでは以下のような操作がブロックされました。(使用したモデルはgpt-image-1) 未成年者(制服を着ているなど、文脈からそう判断される場合を含む)の画像を編集する指示 侮辱的・差別的な言葉をプロンプトに含めること 特定の個人の顔を入れ替えるような指示 これらの制限からAOAIが重視しているのは、少なくとも次の3点だと考えられます。 AOAIが重視する3つのポイント 社会的に弱い立場にある未成年者の保護 差別や侮辱といった言葉の暴力から人間の尊厳を守ること ディープフェイクなどによる誤情報の拡散防止 これを一言で表すなら、「自由を制限するための壁」ではなく、「他者や社会を守るための枠組み」と言えるのではないでしょうか。 自分が誰かを傷つけないため、そして誤って「加害者」にならないためにも、こうした枠組みは非常に参考になります。 AI時代の透明性:「コンテンツクレデンシャル」 最近は画像生成や編集を個人でも気軽に行えるようになったため、意図せず他人を傷つけてしまうリスクも高まっています。 ただしこのような課題に対応するため、AIで生成・編集された画像には多くの場合「 コンテンツクレデンシャル 」と呼ばれるメタデータが埋め込まれています。 これはAI生成の透明性を担保する新しい仕組みであり、 いつ作られたか どんなツールが使われたか 誰が編集したか といった情報が記録されます。 いわば、 デジタル版の栄養成分表示 のようなものです。 この仕組みにより、コンテンツの改ざんや虚偽の検出が可能になります。 言い換えれば、「正しくAIを使った人」がその証明をしながら堂々と発信できる環境が整いつつあるということです。 まとめ 便利さに流されず、「この表現は誰かを傷つけないか?」と自問すること。 その小さな意識の積み重ねこそが、AIを安全に使うための第一歩であり、結果として自分自身や所属する組織を守ることにもつながるのだと思います。
This is Nakanishi from the Developer Relations Group (also serving in the FACTORY E-commerce Development Group and QA Group). This article is a report on the InnerSource Gathering Tokyo 2025 held in Odaiba on September 12th. The term "inner source" has become much more common in recent years. At our company, we continue to make small, steady efforts to promote an inner source culture through creating our tech blog and fostering an engineering culture. I learned a lot from this event and it reaffirmed my belief that our efforts are truly contributing to fostering an inner source culture. I’m writing this report to share this wonderful event more widely and to help promote the culture of inner source. Changing Culture Through "Actions": An Field-Based Approach to Implementing Inner Source Throughout the event, a few common themes consistently echoed through the venue. Breaking down silos isn't about slogans, but the accumulation of small actions . Code isn't the only contribution. And rather than loudly proclaiming what is right, starting small and gaining allies is what drives culture change. Each speaker shared insights from their distinct perspectives and different cultural contexts. Capturing "Silos" Through Gradation Right from the start, the message from the organizers was clear. Inner Source is an attempt to break down internal silos through cultural transformation , but it's not something that can be described in black-and-white terms. The intensity varies by organization. With that in mind, the Chatham House Rule was declared, creating a space where participants could freely take away insights without linking them to specific speakers or companies. Once these essential rules were established, the discussions became lively and engaging, which was fantastic It was the moment I felt glad I had participated from the very start of the event. NTT DOCOMO, which provided the venue ( docomo R&D OPEN LAB ODAIBA ), introduced its facility for "creating, learning, and sharing," equipped with giant LED displays and 5G/edge computing environments. The venue is also open as a co-working hub outside of event days, and I was impressed by how it was designed as a permanent place where engineers can naturally gather . Inner Source Is Not a "License" but a "Method" The keynote speech reinterpreted inner source as a "method" by drawing on the history and practices of OSS (open source software). Public discussions, open access for anyone to participate, and community collaboration —it was said that bringing these OSS practices into the company represents a return to the fundamentals of inner source. The discussion also significantly highlighted contributions beyond code , explicitly stating that reviews, testing, triage, translations, documentation, infrastructure operations, and public relations are all first-class contributions. In discussing review language etiquette, they introduced a principle from the networking community, which is "receive with generosity, express with precision" , and connected it to dialogue etiquette. A suggestion is not a command, but the start of a dialogue . Since others can learn by observing such interactions, the language itself helps shape the culture. The other key factor is its compatibility with Agile . Frequent releases, self-organization, evolving requirements—what OSS has long implemented and agile practices ultimately resemble. Therefore, the method of change follows the same path. Actions change, thoughts change, and culture changes . I was reminded that despite the various terms used, such as so-called engineer culture, there is a commonality in the underlying mindset and behavior. Regarding motivation, recent trends were shared, where fun and learning are joined by career growth and reputation . One example highlighted how a company’s design that supports growth and learning led to a rapid increase in internal contributors. In the Q&A, a practical point was raised: incentives that rely solely on money don’t last . The best approach is to design around three elements which are fun, learning, and recognition. As a strategy against burnout, a step-by-step approach was shared. Rather than speaking to a large group from the start, begin by reaching out to individuals, achieving small wins, and gradually building a base of supporters . An answer to the question about handling difficult behavior was down-to-earth: establish a code of conduct and work to raise the community’s overall 'average' . The concept of creating an inclusive framework, turning others allies rather than excluding was consistently present. https://kdmsnr.com/slides/20250912\_innersource/ Many of these align with More Fearless Change: Strategies for Making Your Ideas Happen , and I highly recommend them for driving new initiatives within your organization. NRI "xPalette": Circulating Capabilities Nomura Research Institute shared insights gained over four years of creating environments that empower engineers' creativity and initiative . They establish reference architectures and individual guides, then circulate insights gained through experimentation in a "Learn → Apply → Feedback" cycle. As this cycle turns, opportunities to participate in projects increase, and new ventures combining multiple technologies begin to happen. Explaining activities in terms of business value and creating a positive spiral from budget allocation to environmental improvement is a realistic approach. Management takes the lead in praising young members for trying things out , supporting their “ just give it a go ” mindset with small allocations of time and budget. This kind of hands-on, tangible management approach was evident throughout. Mitsubishi Electric OSPO/ISPO: Turning External Attention into Internal Momentum Mitsubishi Electric has established a system to run OSPO (Open Source Program Office) and ISPO (InnerSource Program Office) in parallel, starting with fostering the habit of " prepare the platform → publish it openly ." Drawing attention at external events and channeling outside interest back into internal recognition . That style of storytelling is characteristic of large corporations. They showcased how a series of internal events helped certain terms evolve into shared language across the organization . Furthermore, they announced plans to host a conference in Yokohama on November 13th , showing a proactive approach to establishing a platform for the movement. InnerSource Summit 2025 https://innersourcecommons.org/ja/events/isc-2025/ Discussion: Regulations Are Also "Made Together" / Quality Is "Value for the User" The discussion was imbued with the practical insights gained from implementing inner source within a large corporation . What struck me most was the proposal that rules, such as development standards and internal regulations, are exactly the kind of things that should be created through inner source . Involve relevant parties and make the approval process transparent. When handling KPIs, do not explain them solely in terms of monetary value . Track the preliminary factors that influence cost , including team size, code reuse, and review lead time. Regarding the definition of quality, the approach emphasized "value for the user" as the core principle, moving away from measuring solely by the number of defects. In response to questions about how to handle generative AI, a calm and grounded answer was given: whether the creator is human or AI is not the core issue . What is needed is quality management that includes user education, operational design, and feedback loops To overcome cost allocation and budget barriers, a practical approach was shared: "Start within your own team first, build a following, and let the system follow later . " KDDI "KAGreement": Where Open Agreements Become Culture KDDI's presentation is an initiative to articulate "Why we are here . " Through weekly FigJam sessions attended by the Vice President and public sharing on Slack , they refine their working agreement (guidelines for action) together . The discussion is following a pattern very similar to inner source practices such as " public sharing, archive, small and fast action, and cross-team ." The design of holding random breakout sessions during company-wide meetings, mixing departments and seniority levels for dialogue, could be described as an implementation that gradually raises the organization's "average . " Behind the scenes, executive sponsorship is certainly providing support. https://www.docswell.com/s/mitsuba\_yu/KLVRX7-2025-09-12-163618 teamLab: If You Can't Find It, It Might as Well Not Exist teamLab's core theme is creating mechanisms that grow through use . The larger an organization grows, the harder it becomes to see what exists and where it’s located. Therefore, they launched the "InnerSource department" internally , starting by visualizing interests and potential collaborators. Next, they created the InnerSource Portal . Consolidate the repository overview, owner, setup instructions, projects using it , and links to Issues/PRs onto a single page . Issue templates are also categorized into types like "questions, improvements, feature requests..." to enhance ease of writing . Furthermore, they talked about plans to implement titles and awards such as " InnerSource Champion ," "Top contributor," "Legendary Issue," and "Rookie of the Year" with a playful spirit. They are also considering initiatives such as regular release sharing sessions and days where everyone collaborates to create a single internal OSS project with a deadline. The objective is singular. Increase the number of situations where it's "right there when you need it . " https://speakerdeck.com/teamlab/innersource\_gathering\_tokyo2025\_teamlab Summary: Small Successes Become a Culture. What resonated most at this year’s ISGT was the recurring theme across all presentations: “gentle pathways . A UI that makes your first PR less intimidating. The words that make the first review comforting. A title that makes you proud of your first contribution. The accumulation of such small successes raises the community's overall "average" and dissolves silos. I strongly felt that inner source is not a system name, but rather the design of daily small actions . Extra: The Vibe at the Social Gathering Following the event, an InnerSource OST (Open Space Technology) session was held, where discussions continued in separate groups for each theme. The event naturally transitioned into a social gathering, so the atmosphere was lively from the start, with an active exchange of opinions continuing throughout. It was striking to see how many meaningful conversations emerged, not just casual exchanges, because the gathering brought together people genuinely committed to tackling the question of how to improve culture.
はじめに はじめまして、KINTOテクノロジーズ(KTC)でモバイルアプリ(Flutter)の開発を担当しているHand-Tomiです。 アプリをGoogle Play Storeに公開した後、 ストアからインストールしたアプリでのみ Firebase関連のエラーが発生する経験をしたことはありませんか? デバッグビルドや内部テストでは正常に動作していたのに、本番環境でのみ以下のようなエラーログが表示される場合: E Failed to get FIS auth token java.util.concurrent.ExecutionException: ... Caused by: Firebase Installations Service is unavailable. Please try again later. この記事では、このような問題の根本原因と解決方法を段階的に解説します。 問題の症状 デバッグビルド/内部リリース : 正常動作 Play Storeからインストール : Firebase初期化失敗、FCMトークン発行不可 一時的なサーバー障害のように見えますが、実際には リリースビルドのアプリ識別(署名/パッケージ)とFirebase/Google Cloud設定の不一致 が原因です 核心原因の理解 Play Storeの最終署名メカニズム Google Playにアプリを配布すると、最終的なAPK/AABは Google Play App signing key(アプリ署名鍵) で再署名されます。これは開発時に使用する アップロード鍵 とは異なる鍵です。 2箇所への登録が必須 Firebase/Google CloudでAndroidアプリを正しく識別するには、以下の2箇所に Play署名鍵のSHA fingerprint を登録する必要があります: Firebase Console (プロジェクト設定 > Android アプリ): SHA-256 必須 Google Cloud Console (API Key > Android アプリ制限を設定している場合): SHA-1 必須 💡 比喩 : Firebaseは SHA-256パスポート を、API Key制限は SHA-1身分証 を要求します。開発用(デバッグ/アップロード鍵)の身分証だけでは、空港(Playビルド)で通過できません。 解決手順 1. Play署名証明書のSHA取得 Google Play Console にアクセス → 対象アプリを選択 左メニュー: テストとリリース → アプリの完全性 ページを下にスクロールして Play アプリ署名 セクションを見つける 設定を表示 ボタンをクリック アプリ署名鍵の証明書 タブで SHA-1 と SHA-256 をコピー :::message 注意 : 同じ画面に「アップロード鍵証明書」タブも表示されますが、本ガイドで必要なのは アプリ署名鍵の証明書 の値です。SHA-1は Google Cloud Console で、SHA-256は Firebase で使用します。 ::: 2. Firebase ConsoleへのSHA-256登録 Firebase Console → プロジェクト選択 → プロジェクト設定 Your apps でAndroidアプリを選択 SHA certificate fingerprints セクションで Add fingerprint をクリック Play署名鍵のSHA-256 を貼り付けて保存 (推奨) 既存のアップロード鍵やデバッグ鍵のSHA-1/256も登録されているか確認 :::message 設定保存後、数分以内に反映されますが、デバイスキャッシュのため アプリの完全削除 → 再インストール が最も確実です。 ::: 3. Google Cloud ConsoleでのAPI Key制限設定(Android制限を使用している場合) google-services.json の api_key.current_key に対応する API Key にAndroidアプリ制限が設定されている場合は、以下の手順でPlay署名鍵のSHA-1を登録する必要があります。 Google Cloud Console → Firebaseと同じプロジェクトを選択 左メニュー: APIs & Services → 認証情報 リストから該当 API Key を選択 アプリケーションの制限 を確認: 「Android apps」が選択されている場合 : 次のステップに進む 「なし」または他の制限の場合 : このセクションはスキップ可能 パッケージ名 + SHA-1 ペアを追加: パッケージ名: applicationId SHA-1: Play署名鍵のSHA-1 (アップロード鍵ではない) 保存(伝播に数分かかる場合があります) :::message alert この画面では SHA-1のみ入力可能 です。SHA-256入力欄が表示されないのは正常です。 ::: 4. FirebaseOptions検証(推奨) リリースビルドが正しい google-services.json を参照しているか確認します。 ビルド成果物の確認 app/build/generated/res/google-services/<variant>/values/values.xml で以下の値が期待通りか確認: gms_app_id (= mobilesdk_app_id ) project_id gcm_defaultSenderId (= Project number) default_web_client_id / api key 実行時オプションのログ出力 val options = FirebaseApp.getInstance().options Log.d("FB_OPTS", """ appId=${options.applicationId} projectId=${options.projectId} apiKey=${options.apiKey} sender=${options.gcmSenderId} """.trimIndent()) 5. 動作確認 デバイスで アプリデータ削除 または アプリ完全削除 Play Storeから再インストール アプリ起動 → adb logcat で確認: adb logcat | grep -i -E "Firebase|Installation|FCM|AppCheck|Gms" 期待されるログ FirebaseInstallations の FID生成成功 Gms の token retrieval succeeded FirebaseMessaging の**onNewToken(...)**コールバック発生 よくある問題(トラブルシューティング) 1. Play署名値ではなくアップロード鍵を登録 Firebase/Cloud両方で必ず アプリ署名証明書 の値を使用してください。 2. マルチフレーバーでリリースが異なるファイルを使用 app/src/<flavor>/google-services.json の配置ミス apply plugin: "com.google.gms.google-services" がモジュールの最下部にない 3. API Keyが異なる鍵 google-services.json の current_key とCloud Consoleで編集対象のキーが同じか再確認してください。 4. App Check(Play Integrity) Enforce ON 統合前であれば一時的に Enforce OFF → 原因分離後に再度ONにします。 5. R8/ProGuard/Resource Shrink影響 一時的に minifyEnabled false / shrinkResources false でビルドして分離テストします。 6. デバイス/環境問題 Google Play開発者サービスの更新が必要 デバイス時間の自動同期設定 プロキシ/セキュリティアプリによるブロック 最終チェックリスト Play Console → アプリの完全性 → アプリ署名証明書 の SHA-1/256 を取得 Firebase Console → Androidアプリ → SHA-256(必要に応じてSHA-1も)登録 Cloud Console → API Key(Android apps制限設定時) → パッケージ名 + Play署名SHA-1登録 ストアインストール版を 完全削除 → 再インストール 後、FID/FCMトークン発行確認 values.xml および実行時 FirebaseOptions 値の検証 App Check/ProGuard/ネットワーク等の追加要因点検 FAQ Q1. Play Integrity APIの状態が「統合: 未開始」でもSHA登録は必要ですか? はい。状態に関係なく、ストアビルドは Play署名鍵 で署名されます。Firebaseには SHA-256 を必ず登録し、Cloud API KeyにAndroidアプリ制限を設定している場合は SHA-1 も登録する必要があります。 Q2. Cloud Console Android アプリ制限にSHA-256は登録できませんか? いいえ。UI上 SHA-1のみ 入力できます。代わりにFirebase側に SHA-256 を登録してください。 Q3. 登録後も同じエラーが発生する場合は? 以下の順序で点検してください: アプリ完全削除/再インストール リリースビルドの values.xml 確認 App Check Enforce OFFで分離 API Keyマッチング再検証 R8/ProGuard影響確認 まとめ この問題は「 リリースビルドはPlay署名鍵基準 」という点を見落とすとよく発生します。 Firebaseには SHA-256 Cloud API Key(Android制限設定時)には SHA-1 この2つを正しい場所に登録すれば、ほとんどの場合すぐに解決します。 実務では登録後の 再インストール と オプション検証ログ まで一緒にチェックすることで、再発を防ぐことができます。 参考: ローカルkeystoreからのSHA抽出 Play署名鍵はローカルにありませんが、 アップロード鍵/デバッグ鍵 確認用: # keystoreから証明書フィンガープリント表示 (SHA-1/256) keytool -list -v -keystore <your-keystore.jks> -alias <alias-name> # Androidデバッグ鍵 (macOS/Linux) keytool -list -v -keystore ~/.android/debug.keystore \ -alias androiddebugkey -storepass android -keypass android
KINTOテクノロジーズのAndroidエンジニア 山田 剛 です。 本記事では、Android API level 35以上への対応が必須化された今、既存のAndroidアプリを素早くedge-to-edge対応にするためのノウハウを紹介します。 1. はじめに android { defaultConfig { targetSdk = 35 // ... } // ... } 2025年8月31日以降、Google Playストアで公開されるAndroidアプリは、API level 35以上での公開が必須となりました。 すなわち、 targetSdk を35以上の値にしてビルドしたAndroidアプリでなければ、Google Playストアでの新規アプリの公開、および既存アプリのアップデートが受け付けられなくなりました。 しかし、 targetSdk を35以上の値にしてビルドしたアプリを開くと、画面のステータスバーやシステムナビゲーションバーの領域、およびノッチで隠れた画面の領域が、すべてアプリの表示領域になります。 これに伴い、既存のアプリをedge-to-edge対応にする必要があります。すでに対応済みのアプリも多数でしょうが、開発スケジュールが確保できずまだ対応できていない、という開発者にとってはすでに猶予のない状態です。ほとんどの画面では対応済みなのだが、一部の画面でどうにも変な表示が直らない、という状況もあるでしょう。 この記事では、既存アプリを極力早くedge-to-edge対応にするためのノウハウを紹介します。 本記事のおおまかな内容は以下の通りです: View で構成された画面では、コールバックを活用しましょう。 Composable で構成された画面では、 WindowInsets に関係するさまざまな関数を活用しましょう。 View と Composable が混在するアプリでは、ViewとComposableのそれぞれの対策を組み合わせることによって生じる問題を調整するために用意された関数をうまく使いましょう。 リストの内部パディングなどをうまく使い、できるだけスクロール途中の表示でステータスバー、システムナビゲーションバーの領域を活用できるようにしましょう。 2. Viewで構成された画面のedge-to-edge対応 ![](/assets/blog/authors/tsuyoshi_yamada/2-01_2D_basic-right_KINTO-character.svg =125x) くもびぃ 以下は 画像 が縦に流れていく画面を View で構成した簡単なアプリです: class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var imageAdapter: ImageAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) withStyledAttributes(TypedValue().data, intArrayOf(android.R.attr.colorPrimary)) { window.statusBarColor = getColor(0, 0) } setSupportActionBar(binding.toolbar) imageAdapter = ImageAdapter(layoutInflater, 2) binding.recyclerView.adapter = imageAdapter binding.fab.setOnClickListener { _ -> imageAdapter.increment() binding.recyclerView.scrollToPosition(imageAdapter.length - 1) } } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_settings -> { val editText = layoutInflater.inflate(R.layout.item_edit_text, null) as EditText val dialog = AlertDialog.Builder(this) .setView(editText) .setTitle(R.string.image_count) .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } .show() editText.setOnEditorActionListener { _, actionId, _ -> if (actionId != EditorInfo.IME_ACTION_DONE) return@setOnEditorActionListener false editText.text.toString().toIntOrNull()?.let { imageAdapter.length = it } dialog.dismiss() return@setOnEditorActionListener true } true } else -> super.onOptionsItemSelected(item) } } class ImageAdapter(private val inflater: LayoutInflater, initialLength: Int) : RecyclerView.Adapter<ImageAdapter.ViewHolder>() { var length: Int = initialLength set(value) { val incremental = value - field if (incremental == 0) return field = value if (incremental < 0) { notifyItemRangeRemoved(value, -incremental) } else { notifyItemRangeInserted(value - incremental, incremental) } } override fun getItemViewType(position: Int) = position % IMAGE_LIST.size override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ) = ViewHolder(ItemImageBinding.inflate(inflater, parent, false).apply { image.setImageResource(IMAGE_LIST[viewType]) }) override fun onBindViewHolder(holder: ViewHolder, position: Int) { val bias = (position * 0.3F).rem(2F).let { if (it < 1F) it else 2F - it } holder.binding.spaceStart.let { spaceStart -> (spaceStart.layoutParams as? LinearLayout.LayoutParams)?.let { it.weight = bias spaceStart.layoutParams = it } } holder.binding.spaceEnd.let { spaceEnd -> (spaceEnd.layoutParams as? LinearLayout.LayoutParams)?.let { it.weight = 1F - bias spaceEnd.layoutParams = it } } } override fun getItemCount() = length fun increment() { length = length + 1 } class ViewHolder(val binding: ItemImageBinding) : RecyclerView.ViewHolder(binding.root) companion object { private val IMAGE_LIST = listOf( // ... ) } } レイアウトファイルは以下の通りです: <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/layout_appbar" android:layout_width="0dp" android:layout_height="wrap_content" android:background="?colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" style="@style/Widget.MaterialComponents.Toolbar.Primary" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" /> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="0dp" android:layout_height="0dp" android:background="?colorBackgroundFloating" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/layout_appbar" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" android:orientation="vertical" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" app:shapeAppearanceOverlay="@style/ShapeAppearance.App.Circle" app:backgroundTint="?colorSecondary" app:srcCompat="@android:drawable/ic_input_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> これを targetSdk を34に設定してビルドした場合と35に設定してビルドした場合とで、 Android OS 10 以上で実行した画面を比較します: targetSdk = 34 targetSdk = 35 (対策なし) targetSdk が34なら何も問題なく表示されますが、35だとステータスバーがタイトルバーに吸収されて見づらくなり、タイトル自体もカットアウト(カメラなどのセンサーをディスプレイ上に収めた部分の切り欠き)の穴が空き、システムナビゲーションバーの領域もアプリの表示領域に含まれてしまいます。 targetSdk = 35 における エッジ ツー エッジの適用 とは、アプリの表示領域を否応なしにステータスバーやシステムナビゲーションバー、カットアウトの領域にまで拡げてしまうということなのです。 さすがにこの状態のアプリをアプリストアなどにリリースするのはつらいものがあります。もう時間がありませんが、何とかしなければなりません。 2.1. ViewCompat.setOnApplyWindowInsetsListener ビューでコンテンツをエッジ ツー エッジで表示する にて、Viewをedge-to-edgeに対応させるさまざまな方法が紹介されています。 そのうち、汎用性の高い手段として ViewCompat.setOnApplyWindowInsetsListener(View, OnApplyWindowInsetsListener) を使用する方法があります: override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // window.statusBarColor = getColor(0, 0) // edge-to-edgeではstatusBarColorの設定は不要(無効) setSupportActionBar(binding.toolbar) imageAdapter = ImageAdapter(layoutInflater, 2) binding.recyclerView.adapter = imageAdapter binding.fab.setOnClickListener { _ -> imageAdapter.increment() binding.recyclerView.scrollToPosition(imageAdapter.length - 1) } applyWindowInsetsForE2E() } private fun applyWindowInsetsForE2E() { ViewCompat.setOnApplyWindowInsetsListener(binding.layoutAppbar) { v, windowInsets -> val insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updatePadding(left = insets.left, top = insets.top, right = insets.right) WindowInsetsCompat.CONSUMED } ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, windowInsets -> val insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom) WindowInsetsCompat.CONSUMED } ViewCompat.setOnApplyWindowInsetsListener(binding.fab) { v, windowInsets -> val insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() ) v.updateLayoutParams<ViewGroup.MarginLayoutParams> { val margin = resources.getDimensionPixelOffset(R.dimen.fab_margin) bottomMargin = insets.bottom + margin rightMargin = insets.right + margin } WindowInsetsCompat.CONSUMED } } listener の中で WindowInsets$getInsets(Int) に WindowInsetsCompat.Type から適切な関数を組み合わせて引数に与え、インセット(上、左、右、下の空間)の値を得ます。 MainActivity$applyWindowInsetsForE2E() の中で、 AppBarLayout, RecyclerView, FloatingActionButton のそれぞれに対して ViewCompat.setOnApplyWindowInsetsListener(...) を呼び出して設定している点に注意が必要です。 AppBarLayout ではステータスバーとカットアウトの領域分だけ上部のパディングが必要ですが、RecyclerView では上部を気にする必要がないので top のパディングは設定していません。 FloatingActionButton では、end と bottom のマージンを加算して LayoutParams のマージンを変更しています。 他方、RecyclerView では、以下のように android:clipToPadding="false" を追加して、 RecyclerView の表示領域にパディングをするのではなく、 RecyclerView のコンテンツ全体の左右と下部へのパディングとすることによって、下部のシステムナビゲーションバーの部分も表示領域として活用しつつ、スクロールの下端ではコンテンツとシステムナビゲーションが重複しなくて済むようにできます: <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="0dp" android:layout_height="0dp" android:background="?colorBackgroundFloating" android:clipToPadding="false" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/layout_appbar" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" android:orientation="vertical" /> edge-to-edge対応で求められているのは、画面の端から端までを表示に活用しつつ、アプリUIがステータスバーやシステムナビゲーションバーの邪魔にならないように、カットアウトがアプリUIの邪魔にならないように両立させることです。 対策の時間が限られている状況では、ひとまずインセットの部分を背景色のみで潰しておく、という対応で時を稼がざるを得ないこともあるでしょう。それでも、できる限りはedge-to-edgeの長所を活かすように対応したいものです。 targetSdk = 35 (対策あり、スクロール上端) targetSdk = 35 (対策あり、スクロール下端) listener の末尾で WindowInsetsCompat.CONSUMED を返すことで、各Viewのパディングもしくはマージンとして領域を 消費する ことをAndroidのシステムに伝えています。 つまり、AppBarLayout の上部のパディング部分は AppBarLayout に属しているので、パディング部分も含めてXMLレイアウトファイルの android:background="?colorPrimary" という記述によって背景色が設定されます。 この性質によって、edge-to-edgeでは「ステータスバーの背景色」という概念がなくなっているので、API level 35以上では window.statusBarColor ( Window$setStatusBarColor(Int) ) は何も効果がない非推奨の関数に変わっています。 システムナビゲーションバーの高さは3ボタンナビゲーション(下の左画像)か、ジェスチャーナビゲーション(下の右画像)かで変化します。この変化にアプリUIが追随できるように実装する必要があります。 ViewCompat.setOnApplyWindowInsetsListener(...) を活用する方法の優れた点は、記述が非常にわかりやすく、かつアプリやAndroid端末の事情が絡み合う複雑な状況に対応しやすいことです。 MainActivity$applyWindowInsetsForE2E() は、3つのViewに対してそれぞれに必要なパディングの値を WindowInsetsCompat$getInsets(Int) で取得し、その値をパディングもしくはマージンとして設定しています。 そのうち FloatingActionButton のマージンの値の取得時に WindowInsetsCompat.Type.ime() のフラグの指定を含めています。 これで、下の右画像のように、ソフトウェアキーボードなどが表示されているときにその高さの分だけ FloatingActionButton が持ち上がるように実装できます(ボタンをこのように持ち上げる必要があることは多くはないでしょうが、例として挙げてみました)。 このようにアプリの状態変化に追随させるためには、画面の初期設定が終わった後もリスナーのunsetは行わずにに状態を監視させ続ける必要があります。 targetSdk = 35 (対策あり、音声入力UI表示時) targetSdk = 35 (対策あり、ジェスチャーナビゲーション) 画面の回転を可能にしているアプリでは、 ViewCompat.setOnApplyWindowInsetsListener(...) はさらに強力です。 画面が回転するアプリだと、カットアウトは回転ごとに常に移動します。回転なしポートレイト(縦長)のときは上端でステータスバーと重なるので上端ではステータスバーとカットアウトのどちらか高い方のパディングを設定すればよいですが、それ以外の状態ではステータスバーとカットアウトの両方のパディングが必要になります。 システムナビゲーションバーについては、ボタンナビゲーションの場合はランドスケープ(横長)のとき左端または右端に移動、ジェスチャーナビゲーションの場合は常にシステムナビゲーションバーが下端。またタイトルバーは常に上端…と、多数の複雑な組み合わせが存在します。 このときも ViewCompat.setOnApplyWindowInsetsListener(...) は常に妥当なパディング・マージンの値を提供します。 targetSdk = 35 (対策あり、左90°回転ランドスケープ、ボタンナビゲーション) targetSdk = 35 (対策あり、右90°回転ランドスケープ、ジェスチャーナビゲーション) 2.2. ItemDecoration などを使う Viewの構成によっては、多くのViewに新たにコールバックを追加しにくいようなケースもあるかもしれません。そのような場合には、1箇所で取得したインセットの値を共有するような方法でもよいでしょう。アプリがポートレイトのみ対応で画面回転を考慮しなくてよい場合など、インセットの動的な変化があまり起こらないアプリでは、できるだけ静的に設定する処理にするのがエンバグの危険が少ないとも考えられます。Activityで取得したインセットの値をFragmentと共有するようにしてもよいでしょう。 private var insetBottom = 0 override fun onCreate(savedInstanceState: Bundle?) { // ... ViewCompat.setOnApplyWindowInsetsListener(binding.layoutAppbar) { v, windowInsets -> // アプリがポートレイトのみ対応で画面回転を考慮しなくてよい場合 val insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) insetBottom = insets.bottom binding.recyclerView.addItemDecoration(createListBottomSpacingItemDecoration(insetBottom)) binding.fab.updateLayoutParams<ViewGroup.MarginLayoutParams> { val margin = resources.getDimensionPixelOffset(R.dimen.fab_margin) bottomMargin = insets.bottom + margin rightMargin = insets.right + margin } v.updatePadding(left = insets.left, top = insets.top, right = insets.right) WindowInsetsCompat.CONSUMED } } たとえば、 RecyclerView の場合は RecyclerView$addItemDecoration(RecyclerView.ItemDecoration) を使った以下のような関数で RecyclerView のスクロール下端にedge-to-edge対応のパディングを設定することができます。ItemDecorationは主に区切り線を設定するために使われますが、このように決まった位置に空白を設定するだけの目的にも使えます。 アプリの構成によっては、 android:clipToPadding="false" を使ってパディングを設定するよりも少ない変更で済みそうです: fun createListBottomSpacingItemDecoration(insetBottom: Int) = object : RecyclerView.ItemDecoration() { override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { outRect.set(0, 0, 0, if (parent.getChildAdapterPosition(view) < state.itemCount - 1) 0 else insetBottom) } } 3. Composableで構成された画面のedge-to-edge対応 Jetpack Compose 1.0 が公開されて4年経過した今、既存のプロジェクトではViewからComposableへの移行がかなり進んだ、もしくは最近立ち上がったプロジェクトでは最初から大半のUIをComposableで構成している、という開発プロジェクトも多いでしょう。 先ほどのViewベースのアプリと同様のComposeベースのアプリは、以下のような記述になるでしょう。なお本記事では、compose-bom 2024.06.00以降、Material3を用いるものとします: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { E2ESampleTheme { val showsDialog = remember { mutableStateOf(false) } val isFabClicked = remember { mutableStateOf(false) } Scaffold( topBar = { Row( modifier = Modifier .fillMaxWidth() .heightIn(64.dp) .background(MaterialTheme.colorScheme.primary), verticalAlignment = Alignment.CenterVertically ) { Text( stringResource(R.string.app_name), modifier = Modifier .padding(start = 16.dp) .weight(1F, true), fontSize = 20.sp, fontWeight = FontWeight.W600, maxLines = 1, overflow = TextOverflow.Ellipsis ) TextButton( modifier = Modifier.padding(end = 8.dp), onClick = { showsDialog.value = true }, colors = ButtonDefaults.buttonColors(contentColor = MaterialTheme.colorScheme.onPrimary) ) { Text( stringResource(R.string.image_count), fontSize = 16.sp, fontWeight = FontWeight.W600 ) } } }, floatingActionButton = { FloatingActionButton( modifier = Modifier .padding(dimensionResource(R.dimen.fab_margin)), shape = CircleShape, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary, onClick = { isFabClicked.value = true } ) { Icon(Icons.Filled.Add, "One more") } }, content = { innerPadding -> ImageColumn(Modifier.padding(innerPadding), showsDialog, isFabClicked) } ) } } withStyledAttributes(TypedValue().data, intArrayOf(android.R.attr.colorPrimary)) { window.statusBarColor = getColor(0, 0) } } } @DrawableRes private val IMAGE_LIST = listOf( // ... ) @Composable fun ImageColumn(modifier: Modifier = Modifier, showsDialog: MutableState<Boolean>, isFabClicked: MutableState<Boolean>) { var length by remember { mutableIntStateOf(2) } Box(modifier = modifier.fillMaxSize()) { val lazyListState = rememberLazyListState() LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) { items(length, key = { it }) { index -> Box(Modifier.fillParentMaxWidth()) { val bias = (index * 0.6F).rem(4F).let { if (it < 2F) it - 1F else (4F - it) - 1F } Image( modifier = Modifier .padding(8.dp) .align(BiasAlignment(horizontalBias = bias, verticalBias = 0F)) .animateItemPlacement(), // If compose 1.8.0 or upper, use .animateItem() painter = painterResource(IMAGE_LIST[index % 4]), contentDescription = null ) } } } val coroutineScope = rememberCoroutineScope() LaunchedEffect(isFabClicked.value) { if (isFabClicked.value) { length += 1 isFabClicked.value = false coroutineScope.launch { lazyListState.animateScrollToItem(length - 1) } } } if (showsDialog.value) { var numberText by remember { mutableStateOf("") } AlertDialog( onDismissRequest = { showsDialog.value = false }, title = { Text(stringResource(R.string.image_count)) }, confirmButton = {}, dismissButton = { TextButton(onClick = { showsDialog.value = false }) { Text(stringResource(R.string.cancel)) } }, text = { TextField( numberText, { text -> numberText = text.filter { it.isDigit() } }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardActions = KeyboardActions { length = numberText.toIntOrNull() ?: 0 showsDialog.value = false }, singleLine = true ) } ) } } } Viewベースの場合と同様、 targetSdk を34に設定してビルドした場合と35に設定してビルドした場合で比較します。 やはりタイトルバーでは問題が起きていますが、Viewのときとは異なり FloatingActionButton はシステムナビゲーションバーとは重なっていません。これは Scaffold(...) の content パラメータに設定している関数オブジェクトの引数 innerPadding を参照してパディングを設定しているためです。 また、 Scaffold(...) の topBar パラメータに設定している関数オブジェクトの中で TopAppBar(...) を使えば、ステータスバーの領域にパディングが自動的に設定されます。 Viewベースでの開発と比べてJetpack Composeの各関数では、このようにedge-to-edgeへの対応が考慮されている部分が多く、 宣言的UI の概念によってより直感的な記述によるedge-to-edge対応が可能になっています。 もっとも、現時点では TopAppBar(...) を使うには @ExperimentalMaterial3ExpressiveApi が必要です。ここでは、experimental API がプロジェクトの制約等で使えず、またシステムナビゲーションバーの部分に縦スクロールのコンテンツを表示させる必要がある、という場合の実装を考えてみましょう。 targetSdk = 34 targetSdk = 35 (対策なし) 3.1. WindowInsets のプロパティ (androidx.compose.foundation.layout.) WindowInsets のプロパティおよび関数を使って、ステータスバーやシステムナビゲーションバー、カットアウトの領域のインセットを取得できます。 WindowInsets.asPaddingValues() を使ってインセットをパディングの値に変換し、 Modifier などに適用することで、Composableとエッジとの間に適切な距離をとらせることができます: override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { E2ESampleTheme { val showsDialog = remember { mutableStateOf(false) } val isFabClicked = remember { mutableStateOf(false) } val safeDrawingInsets = WindowInsets.safeDrawing.asPaddingValues() val direction = LocalLayoutDirection.current Scaffold( topBar = { Row( modifier = Modifier .background(MaterialTheme.colorScheme.primary) .padding( start = safeDrawingInsets.calculateStartPadding(direction), top = safeDrawingInsets.calculateTopPadding(), end = safeDrawingInsets.calculateEndPadding(direction) ) .fillMaxWidth() .heightIn(64.dp), verticalAlignment = Alignment.CenterVertically ) { // この部分は不変 ... } }, floatingActionButton = { FloatingActionButton( modifier = Modifier.padding(end = safeDrawingInsets.calculateEndPadding(direction)), shape = CircleShape, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary, onClick = { isFabClicked.value = true } ) { Icon(Icons.Filled.Add, "One more") } }, content = { innerPadding -> ImageColumn( Modifier.padding(top = innerPadding.calculateTopPadding()), showsDialog, isFabClicked ) } ) } } // window.statusBarColor = getColor(0, 0) // edge-to-edgeではstatusBarColorの設定は不要(無効) } @Composable fun ImageColumn(modifier: Modifier = Modifier, showsDialog: MutableState<Boolean>, isFabClicked: MutableState<Boolean>) { var length by remember { mutableIntStateOf(2) } val direction = LocalLayoutDirection.current val navigationBars = WindowInsets.navigationBars.asPaddingValues() val verticalBars = WindowInsets.displayCutout.union(navigationBars).asPaddingValues() Box(modifier = modifier .padding( start = verticalBars.calculateStartPadding(direction), end = verticalBars.calculateEndPadding(direction) ) .fillMaxSize() ) { val lazyListState = rememberLazyListState() val bottomPadding = navigationBars.calculateBottomPadding() LazyColumn( state = lazyListState, modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(bottom = bottomPadding) ) { items(length, key = { it }) { index -> Box(Modifier.fillParentMaxWidth()) { val bias = (index * 0.6F).rem(4F).let { if (it < 2F) it - 1F else (4F - it) - 1F } Image( modifier = Modifier .padding(8.dp) .align(BiasAlignment(horizontalBias = bias, verticalBias = 0F)) .animateItemPlacement(), // If compose 1.8.0 or upper, use .animateItem() painter = painterResource(IMAGE_LIST[index % 4]), contentDescription = null ) } } } // ... } } 各Composableの Modifier に対して Modifier.windowInsetsPadding(WindowInsets) を使えればもう少し簡潔に書けますが、画面の回転に応じて上、左右、下のそれぞれで異なる処理を行わせるため、細かく設定を計算しています。 要領は WindowInsetsCompat の使い方とよく似ています。 WindowInsets の方が少し簡略化されて直感的にわかりやすくなっています。 WindowInsets.Companion.statusBars でステータスバー、 WindowInsets.Companion.navigationBars でシステムナビゲーションバーのインセットを表します。 また、複数のインセットを組み合わせたインセットを考えることもできます。 WindowInsets.Companion.systemBars は WindowInsets.statusBars.union(WindowInsets.captionBar).union(WindowInsets.navigationBars) と同じ、 WindowInsets.Companion.safeDrawing は WindowInsets.systemBars.union(WindowInsets.ime).union(WindowInsets.displayCutout) と同じです。 ここで WindowInsets.union(WindowInsets) は上、左、右、下のそれぞれで各インセットの最大値をとる関数です。たとえば回転なしポートレイトのときは上端にはステータスバーとカットアウトがあり、両者のうち高い方の高さを上端のインセットとする、という演算を行います(高さの最大値の距離をとることでステータスバーとカットアウトの両方を避けられるため)。 インセットの値を得て、 Scaffold(...) の topBar と floatingActionButton 、 LazyColumn(...) のそれぞれに適切なパディングの値を与えます。Viewベースの場合にはコールバックという手続き的なコードでパディングやマージンを設定しましたが、 宣言的UI 概念のComposeではパラメータのように与えることができ、より直感的にわかりやすくなっているように思います。 floatingActionButton に対しては、ランドスケープ時の対策としてインセットの end の値のみにパディングを設定しています。 LazyColumn(...) では、Viewベースの RecyclerView にて android:clipToPadding="false" を設定したのと同様にスクロールするコンテンツの全体に対して下端のパディングを与えるため、 Modifier.padding(Dp) ではなく contentPadding パラメータに safeDrawingInsets.calculateBottomPadding() を与えています。 targetSdk = 35 (対策あり、ボタンナビゲーション) targetSdk = 35 (対策あり、ジェスチャーナビゲーション) 3.2. レイアウトの内側にパディングを設定する 2.2. のItemDecorationのように、LazyLayoutの内側に空白を設定することでedge-to-edgeにすることもできます: fun ImageColumn(modifier: Modifier = Modifier, showsDialog: MutableState<Boolean>, isFabClicked: MutableState<Boolean>) { var length by remember { mutableIntStateOf(2) } val direction = LocalLayoutDirection.current val navigationBars = WindowInsets.navigationBars.asPaddingValues() val verticalBars = WindowInsets.displayCutout.union(WindowInsets.navigationBars).asPaddingValues() Box( // ... ) { val lazyListState = rememberLazyListState() val bottomPadding = navigationBars.calculateBottomPadding() LazyColumn( state = lazyListState, modifier = Modifier.fillMaxSize() ) { items(length, key = { it }) { index -> // ... } item { Spacer(modifier = Modifier.height(bottomPadding)) // <- } } // ... } // ... } LazyColumn(...) の末尾に item { Spacer(...) } を追加するだけです。仮に、タイトルバーがなくステータスバーとカットアウトの分のパディングが必要であれば、 LazyColumn(...) の先頭に同様の item を追加すればよいでしょう。 contentPadding よりも 宣言的 でわかりやすいと言えるかもしれません。 このように、レイアウトの内側にパディングを設定することも有力な手段です。Jetpack Compose には、このような柔軟な対応がしやすいという利点があります。 4. ViewとComposableが混在する画面のedge-to-edge対応 Jetpack Compose の公開後、ViewベースのアプリのComposable化を鋭意進めているが、残っているViewのすべてはすぐにはComposable化できない、というプロジェクトも多いでしょう。実際、WebViewなど、pure composable なソリューションがまだ存在しないUI部品も残っており ^1 、Viewとの完全なお別れはまだ非現実的、というプロジェクトが多いことと推察いたします。 そんなアプリでは ComposeView などを使ってViewとComposableを混在させて画面を構成していると思われますが、これに対して上記で紹介したようなテクニックを使う際、状況が複雑化し、思わぬ問題が起きることがあります。 class MainActivity : AppCompatActivity() { private lateinit var imageAdapter: ImageAdapter val showsDialog = mutableStateOf(false) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) E2ESampleTheme { ImageColumnScaffold(showsDialog) } } } withStyledAttributes(TypedValue().data, intArrayOf(android.R.attr.colorPrimary)) { window.statusBarColor = getColor(0, 0) } setSupportActionBar(findViewById(R.id.toolbar)) } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_settings -> { showsDialog.value = true true } else -> super.onOptionsItemSelected(item) } } @Composable fun ImageColumnScaffold(showsDialog: MutableState<Boolean>) { val isFabClicked = remember { mutableStateOf(false) } Scaffold( floatingActionButton = { FloatingActionButton( shape = CircleShape, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary, onClick = { isFabClicked.value = true } ) { Icon(Icons.Filled.Add, "One more") } }, content = { innerPadding -> ImageColumn(Modifier.padding(innerPadding), showsDialog, isFabClicked) } ) } レイアウトXMLファイルには ComposeView が含まれます: <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/layout_appbar" android:layout_width="0dp" android:layout_height="wrap_content" android:background="?colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" style="@style/Widget.MaterialComponents.Toolbar.Primary" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" /> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/layout_appbar" /> </androidx.constraintlayout.widget.ConstraintLayout> ImageColumn.kt は 3. と同じものを使います。 スクロール部分やフローティングアクションボタンはComposeで実装しているが、タイトルバーとメニューの処理は AppCompatActivity$setSupportActionBar(Toolbar) を使っている、という例です。この例は該当しませんが、たとえばFragmentで多くの画面を作っているアプリでは、このような構成になることが多そうですね。 このアプリで targetSdk を34→35としてビルドすると: targetSdk = 34 targetSdk = 35 (対策なし) ステータスバー・カットアウトとタイトルが重なるのは承知の上ですが、それに加えてタイトルバーとスクロール部分の間に奇妙な隙間が生じています。 これは Scaffold(...) の content に渡している innerPadding がステータスバーとシステムナビゲーションバーの領域のパディングの値を持っているために起こっています。 この画面では、ステータスバー・カットアウトの領域を確保するのは AppBarLayout の責務、すなわちViewの責務であり、Composableは余計なことをしてはいけません。この画面に限って対策するなら Scaffold(...) の content に渡すパディングを 0dp にすればよいのですが、 Scaffold(...) を使うComposableが大きな関数で、ある画面では純粋なComposableからなる画面から呼び出され、ある画面では ComposeView を含むViewベースの画面から呼び出され…というように多くの画面で共通に使われていて、、多岐にわたる処理を引き受けていたりすると、修正が大変です。 こういった場合に対処するため、Jetpack Composeには便利な関数が用意されています。 4.1. Modifier.consumeWindowInsets(WindowInsets) 以下のコードで解決します: class MainActivity : AppCompatActivity() { val showsDialog = mutableStateOf(false) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) E2ESampleTheme { ImageColumnScaffold( Modifier.consumeWindowInsets(WindowInsets.systemBars), // <- showsDialog ) } } } // window.statusBarColor = getColor(0, 0) // edge-to-edgeではstatusBarColorの設定は不要(無効) setSupportActionBar(findViewById(R.id.toolbar)) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.layout_appbar)) { v, windowInsets -> val insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updatePadding( left = insets.left, top = insets.top, right = insets.right, ) WindowInsetsCompat.CONSUMED } } // ... } @Composable fun ImageColumnScaffold(modifier: Modifier = Modifier, showsDialog: MutableState<Boolean>) { val isFabClicked = remember { mutableStateOf(false) } Scaffold( modifier, floatingActionButton = { FloatingActionButton( modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()), shape = CircleShape, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary, onClick = { isFabClicked.value = true } ) { Icon(Icons.Filled.Add, "One more") } }, content = { innerPadding -> ImageColumn(Modifier.padding(innerPadding), showsDialog, isFabClicked) } ) } ステータスバーの問題は、Viewベースの方法 ViewCompat.setOnApplyWindowInsetsListener で解決します。 隙間の問題は、 Scaffold(...) の modifier 引数に Modifier.consumeWindowInsets(WindowInsets) を追加したものを渡すことで解決します。これは、Composableが systemBars の領域、すなわちステータスバーとシステムナビゲーションバーの領域を 消費 することを宣言するものです。 これで隙間だった部分にも Scaffold(...) のコンテンツが表示されて正常な表示に戻りますが、そのかわりに FloatingActionButton(...) のようなUIも systemBars の領域に入り込むようになるため、 FloatingActionButton(...) の modifier に Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) を追加して、システムナビゲーションバーやカットアウトの領域を避けるように明示する必要が生じます。 ImageColumn(...) には 3.1. と同じものが使えます。このときと同じく、画面の左右の障害物を避けつつ、下端には LazyColumn(...) の contentPadding を設定し、スクロールコンテンツの表示をシステムナビゲーションバーなどの領域にも許す一方、スクロールの下端がシステムナビゲーションバーなどの領域に重ならないようにしています。 5. まとめ Android 15 対応が必須となった今、次のアプリリリース・アプリアップデートの日までに大急ぎでedge-to-edgeに対応しなければならない、という開発者の皆さんにも助けになることを願って、Viewベース、Composableベース、そしてViewとComposableが混在する場合のedge-to-edge対応の方法を紹介しました。非常に単純な画面例をサンプルにしていますが、それでも状況によってはかなり複雑な思考を要する場合があります。開発プロジェクトによっては、ほとんどの問題は解決しているが一部の画面がまだ解決していない、ということもあるでしょう。 edge-to-edge対応にはさまざまなツールが提供されています。ここで紹介したテクニックのうち試していないものがあれば、手を替え品を替えて試してみてください。また筆者自身、上記のような簡単なサンプルでもさまざまな対策を組み合わせて試すことで多くの学びがありました。何らかの形で本記事が皆さんの助けになれば幸いです。 6. 参考文献 Android API reference Behavior changes: Apps targeting Android 15 or higher Display content edge-to-edge in views Insets handling tips for Android 15’s edge-to-edge enforcement [Android] Modifier.consumeWindowInsetsとModifier.windowInsetsPaddingの動作まとめ|kaleidot.net
OpenAI vs Google 画像編集対決 gpt-image-1 と Gemini 2.5 Flash Image の"一貫性"を検証してみた 近年、OpenAIの ChatGPT、Googleの Gemini、Anthropicの Claude が生成AIの主要プレイヤーとして存在感を高めていますが、このうち画像の生成・編集を提供しているのは OpenAI と Googleの2社です。 本記事では、OpenAIの gpt-image-1(2025年7月時点)と、GoogleのGemini 2.5 Flash Image(通称 Nano Banana、Web UI 2025年8月時点)に焦点を当て、画像の 一貫性と日本語テキストの扱いを中心に、実際の出力例を交えて比較します。 1. gpt-image-1、Flash Image とは何か gpt-image-1(OpenAI) ChatGPTの「4o ImageGeneration」の基盤モデル。強力な生成・編集能力を持ち、インペインティング(マスク編集)**に対応。 なお、フル機能はAPI経由での利用が前提です。 Gemini 2.5 Flash Image(Google) 高速・軽量な画像生成機能で、参照画像を用いた生成に対応。無料ユーザーでも使える点が特徴です(愛称 Nano Banana)。 2. 画像生成AIが抱える弱点:一貫性 AIで画像を繰り返し生成・編集すると、 「元の見た目から少しずつズレていく」問題 が発生しがちです。 いわゆる「一貫性の欠如」で、人物の顔・体型・衣服の質感、背景の構造などが回数を重ねるほど変化してしまいます。 Flash Image(2025年8月)はこの点が比較的安定しており、登場直後からSNS上でも話題に。 gpt-image-1も(2025年7月)に導入されたパラメータinput_fidelityにより、編集時の一貫性が向上しました。 3. アウトプット比較①:人物のポーズ編集 お題:車の後部座席の画像に、家族写真の人物たちを自然に座らせる。 3-1. gpt-image-1 gpt-image-1は複数画像の直接参照(A画像にB画像の人物を配置)ができないため、事前に簡易合成(下処理)を実施。 家族写真を雑に切り抜いて座席画像の上に重ねました。もちろん input_fidelity = high を設定しています。 ![雑な合成画像](/assets/blog/authors/aoshima/image_edit/03.jpg =50%x) 使用したプロンプト Make this family photo look natural and realistic: - Fix lighting to match car interior lighting - Add natural shadows under people - Adjust color temperature to match - Make people look naturally seated - Blend edges smoothly - Keep faces unchanged but make them fit the scene - Add subtle reflections on windows if visible 出力例 ![gpt-image-1の出力画像01](/assets/blog/authors/aoshima/image_edit/04.jpg =50%x) 所感:服のディテールや内装の細部に差異はあるものの、座り姿勢や影の収まりも一度の生成で十分自然に見えるものが出力されました。input_fidelityの効果か、人物の顔や大きさなども違和感の少ない出来栄えでした。 3-2. Gemini 2.5 Flash Image 参照画像機能 を使用し、座席画像と家族写真を指定。 同等の意図で以下を入力しました。 使用したプロンプト In the image of the car’s back seat, place the three people from the provided family photo, making it look natural as if they are sitting together. - Fix lighting to match car interior lighting - Add natural shadows under people - Adjust color temperature to match - Make people look naturally seated - Blend edges smoothly - Keep faces unchanged but make them fit the scene - Add subtle reflections on windows if visible - Make clothing wrinkles look natural for sitting position 出力例(抜粋) 所感:複数回の試行で人物スケールや座り姿勢の一貫性が向上。 参照画像ありの方が良質でした。とくに車内の構造・材質は高い一貫性で再現。多少気になる点はあるものの、手軽にここまで整う点は大きな強みです。 参照画像なしの出力(参考) 所感:大きな傾向変化は少なめ。参照画像ありの方が安定という結論です。 3-3. 人物編集のまとめ gpt-image-1 :下処理の一手間はあるが、精度の高い合成が可能。input_fidelityにより人物の一貫性が保ちやすい。 FlashImage :参照画像機能で手軽に高品質。数回のリトライで座り姿勢・サイズ感が十分に整う。 共通 :内装や照明など背景一貫性は両者とも良好。 4. アウトプット比較②:文字を使った編集(日本語) 課題意識 :一貫性に加え、生成AIは 日本語テキストの精密再現 が難しい傾向があります。 そこで、人物が手に持つ雑誌の表紙を「日本語タイトル・特集文言」に差し替えるタスクで比較しました。 ![雑誌を抱える画像](/assets/blog/authors/aoshima/image_edit/16.jpg =50%x) 使用したプロンプト(日本語) 手に抱えている雑誌を以下の内容に置き換えてください。 - 日本の雑誌で、「旅立ち」というタイトル - おしゃれでモダンな方向性 - 寺院の特集で表紙はお寺の写真をフィーチャー - 表紙にはコンテンツ紹介の文言をレイアウト ※ タイトルを日本語にしたいため、プロンプトは 日本語で統一 。 4-1. gpt-image-1(インペインティング使用) 雑誌部分を マスク指定 して編集(自作アプリで実行)。 ![雑誌を抱える画像マスクあり](/assets/blog/authors/aoshima/image_edit/17.jpg =50%x) 出力例 \ 所感:タイトル「旅立ち」は正しく日本語で生成。ただし細かい本文テキストは崩れがち。 4-2. Flash Image(プロンプトのみ) インペインティング非対応のため、 全体をプロンプト指定 で実行。プロンプト内容は同等。 出力例 所感:雰囲気は再現できるものの、細部の日本語テキストの精密さはgpt-image-1 に一歩譲る印象。 5. 結論と使い分け 観点 gpt-image-1 Flash Image 2.5 タイトル再現 正確に日本語で出る 雰囲気重視 小文字の再現 崩れやすい 難あり 操作性 マスク機能で場所の指定が可能 プロンプトで指定 6. 結果まとめ 観点 gpt-image-1 Flash Image 2.5 一貫性 高い(input_fidelityで強化) 高い(参照画像で安定) 編集機能 マスク編集対応 参照画像対応 日本語テキスト タイトルは良好 雰囲気重視 利便性 API経由で利用(上級者向け) Web UIで手軽に利用可能 要点まとめ 精度・制御力重視 なら gpt-image-1 (特にマスク編集が活きるタスク)。 手軽さ・スピード重視 なら Flash Image (参照画像を活用)。 日本語テキスト は両者とも「雰囲気」は出せるが、 小さな文字や本文の精緻さ はまだ発展途上。 どちらも非常に完成度が高く、特に「全体の雰囲気が崩れない」という点で、以前の世代とは段違いです。 継続的に検証を進めながら、プロンプト設計や生成パラメータのチューニングについてはもちろん、新しいモデルについての検証なども今後共有していく予定です。
OpenAI vs Google Image Editing Showdown Testing the Consistency of gpt-image-1 and Gemini 2.5 Flash Image In recent years, OpenAI's ChatGPT, Google's Gemini, and Anthropic's Claude have emerged as major players in generative AI. Among these, OpenAI and Google offer AI models with image generation and editing capabilities. This article focuses on OpenAI's gpt-image-1 (as of July 2025) and Google's Gemini 2.5 Flash Image (nicknamed Nano Banana, Web UI as of August 2025) to compare them through actual output examples with an emphasis on image consistency and Japanese text handling. 1. What Are gpt-image-1 and Flash Image? gpt-image-1 (OpenAI) The underlying model for ChatGPT's 4o ImageGeneration. It has powerful generation and editing capabilities and supports inpainting (mask-based editing). Note that full functionality requires API access. Gemini 2.5 Flash Image (Google) The model has a fast, lightweight image generation feature that supports generation using reference images. One of the notable characteristics of the model (nicknamed Nano Banana) is its accessibility with a free user account. 2. A Weakness of Image Generation AI: Consistency When repeatedly generating and editing images with AI tools, you may often face a problem where the image appearance gradually drifts from the original one. This so-called lack of consistency results in changes in faces, body shapes, clothing textures, and background structures in an image as the number of generation iterations increases. Flash Image (August 2025) is relatively stable in this regard and became a topic of discussion on social media immediately after its release. gpt-image-1 also improved editing consistency through the input_fidelity parameter introduced in July 2025. 3. Output Comparison 1: Editing Human Poses Task: Naturally seat the people from a family photo on the right side in a vehicle's back seat in an image on the left side. 3-1. gpt-image-1 Since gpt-image-1 cannot directly reference multiple images (placing people from image B into image A), preprocessing with a rough composite was performed beforehand. The family photo was roughly cut out and overlaid on the seat image. Of course, input_fidelity = high was set. ![雑な合成画像](/assets/blog/authors/aoshima/image_edit/03.jpg =50%x) Prompt I Used Make this family photo look natural and realistic: - Fix lighting to match car interior lighting - Add natural shadows under people - Adjust color temperature to match - Make people look naturally seated - Blend edges smoothly - Keep faces unchanged but make them fit the scene - Add subtle reflections on windows if visible Output Example ![gpt-image-1の出力画像01](/assets/blog/authors/aoshima/image_edit/04.png =50%x) Impressions: While there are differences in clothing details and vehicle interiors, the sitting posture and shadow placement looked sufficiently natural only in a single generation. However, the color tone appears to have become yellowish. Facial consistency does not seem to be well maintained. 3-2. Gemini 2.5 Flash Image Using the reference image feature , the seat image and family photo were specified. I entered the following prompt for the same purpose of image generation using gpt-image-1. Prompt Used In the image of the car's back seat, place the three people from the provided family photo, making it look natural as if they are sitting together. - Fix lighting to match car interior lighting - Add natural shadows under people - Adjust color temperature to match - Make people look naturally seated - Blend edges smoothly - Keep faces unchanged but make them fit the scene - Add subtle reflections on windows if visible - Make clothing wrinkles look natural for sitting position Output Examples (Selection) Impressions: While facial consistency is high, there appears to be some variation in balance.\ 3-3. Summary of Human Editing gpt-image-1 : Requires preprocessing effort but enables high-precision compositing. Flash Image : Easy to achieve high quality with the reference image feature. Sitting posture and people's size become sufficiently adjusted with a few generation retries. Common : Background consistency for both interiors and lighting are good. 4. Output Comparison 2: Editing with Text (Japanese) Concern : In addition to consistency, generative AI often struggle with precise reproduction of Japanese text . Therefore, we compared AI models by assigning them to the task of replacing the magazine cover held by a person with a Japanese title and feature text. ![雑誌を抱える画像](/assets/blog/authors/aoshima/image_edit/09.jpg =50%x) Prompt I Used (in Japanese) 手に抱えている雑誌を以下の内容に置き換えてください。 - 日本の雑誌で、「旅立ち」というタイトル - おしゃれでモダンな方向性 - 寺院の特集で表紙はお寺の写真をフィーチャー - 表紙にはコンテンツ紹介の文言をレイアウト *Note: The prompt was written in Japanese from the beginning to the end because we wanted the magazine title to be converted into Japanese. 4-1. gpt-image-1 (Using the Inpainting Feature) Output Examples \ Impressions: The magazine title Tabidachi was accurately generated in Japanese. However, smaller feature text is prone to be distorted. 4-2. Flash Image (Only Unsing the Above Prompt) Since inpainting is not supported, I executed image generation, specifying the entire image via prompt . The prompt content was same as the above-mentioned one. Output Examples Impressions: While the appearance is reproduced, the precision of generating detailed Japanese text falls slightly behind gpt-image-1. 5. Conclusions and Use Cases Aspect gpt-image-1 Flash Image 2.5 Title reproduction Accurately outputs in Japanese Appearance-based Small text reproduction Prone to distortion Difficult 6. Results Summary Aspect gpt-image-1 Flash Image 2.5 Consistency High (enhanced with input_fidelity) High (stable with reference images) Editing features Supports mask editing Supports reference images Japanese text Japanese titles is properly reproduced Appearance-based Convenience Used via API (for advanced users) Easily accessible via Web UI Key Takeaways Choose gpt-image-1 for balance, precision, and control . Choose Flash Image for convenience and speed . As for Japanese text , both models can generate text that looks like Japanese, but their precision of generating Japanese small text and body sentence still needs improvements. Both are highly polished, and particularly in terms of maintaining the overall appearance without degradation, they are leagues ahead of previous generation models. Continuing for our verification, we plan to share findings on prompt design, generation parameter tuning, and testing of new models in the future.
技術広報Gの中西です。(FACTORY EC開発G・QAグループ兼務)こちらの記事は先日9/12にお台場で開催された InnerSource Gathering Tokyo 2025 についてのレポートになります。 インナーソースという言葉を耳にする機会は、ここ数年でぐっと増えてきました。弊社でもテックブログの開発やエンジニア文化の醸成を通じて、インナーソースカルチャーを推進するための小さな積み重ねを続けています。 今回のイベントでは学びが多く、私たちの取り組みこそがインナーソースカルチャーにつながっていると改めて確信しました。素晴らしい内容を多くの方と共有し、インナーソースという文化を広げていきたい — その思いから、このレポートを書いています。 「ふるまい」から文化を変える、現場発のインナーソース実装論 会場に流れていたのは、終始いくつかの共通トーンでした。 サイロを壊すのはスローガンではなく「ふるまい」の累積だ ということ。コードだけが貢献ではないこと。そして、正しさを声高に説くより、 小さく始めて仲間を増やす ことが文化を動かすということでした。登壇者それぞれの別の言葉、カルチャーの異なる現場の共有をされていました。 グラデーションで捉える「サイロ」 冒頭、運営からのメッセージは明快だった。 インナーソースは、文化的変化で社内のサイロを「壊す」ための試み だが、0/1で語れるものではない。組織ごとに濃淡がある。その前提で、 チャタムハウスルール(Chatham House Rule) を宣言し、登壇者や企業名に紐づけず内容を安心して持ち帰れる場にしていました。こういった大前提となるルールが定められていると積極的な生きた議論が出来て素晴らしいですね。イベントの始まりから参加して良かったと思えた瞬間です。 会場( docomo R&D OPEN LAB ODAIBA )を提供したNTTドコモさんは、巨大LEDや5G/エッジ環境まで備えた「作る・学ぶ・発信する」施設を紹介されていました。イベント日以外もコワーキングとして開放されており、「 エンジニアが自然に集まれる恒常的な場 」を設計していることが印象に残っています。 インナーソースは「ライセンス」ではなく「やり方」 基調講演は、OSS(オープンソースソフトウェア)の歴史と作法を手がかりに、インナーソースを「やり方」として捉え直した。 公開された議論・誰でも参加できる入口・コミュニティでの協働 ― このOSSの作法を、 社内に移植する のがインナーソースだという原点回帰だということが語られていました。 「 コード以外の貢献 」に光を当てた点も大きく、レビュー、テスト、トリアージ、翻訳、ドキュメント、インフラ運用、広報までが一級の貢献だと明言されていました。レビュー言語の作法に触れたくだりでは、 「受け取りは寛容に、出す言葉は厳密に」 というネットワーク界隈の原則を、対話の作法に重ねて紹介していました。 指摘は命令ではなく対話の始まり 。それを見て学ぶ第三者もいるからこそ、言葉が文化になるといえます。 もうひとつの軸は アジャイルとの親和性 だ。頻繁なリリース、自己組織化、要求の進化 ― OSSが長く実装してきたものと、アジャイル実践は結局似ている。だから変え方も同じ筋道をたどる。 行動を変え、考えが変わり、文化が変わる 。いわゆるエンジニアカルチャーなど、言葉の選択は色々あるが根本にある考え方や行動に関して共通するものを持っていると改めて感じました。 動機の話では、楽しさや学びに加えて キャリアや評判 が混ざる近年の傾向が紹介されていました。実例としては、 会社が成長・学習を後押しする設計 により、社内の貢献者が短期間で大幅に増えた事例が語られました。 質疑は実務的では、インセンティブは お金だけに寄ると長続きしない 。楽しさと学び、そして評価の三点留めで設計するのが良い。バーンアウト対策については、 最初から大勢に語らず、一人ひとりに声をかけて小さく勝ち、賛同者を増やす という手順が共有された。扱いにくい振る舞いに向き合う質問には、 行動規範を用意し、コミュニティ全体の「平均値」を上げる という堅実な答え。排除ではなく、 転じて味方になる構図 をつくる発想が貫かれていた。 https://kdmsnr.com/slides/20250912_innersource/ これらは Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン のパターンに当てはまるものが多く、組織の中で新たなことを推進するうえで是非オススメしたい。 NRI「xPalette」:ケイパビリティを循環させる 野村総研さんは、 エンジニアの創造性と主体性 を解放する場づくりの四年分の知見を語った。リファレンスアーキテクチャと個別ガイドを整え、各自が試した知見を 「学ぶ→現場適用→フィードバック」 で循環させる。 この循環が回ると、プロジェクト参画の機会が増え、複数の技術を組み合わせた 新しい事業の芽 も立ち上がる。活動を 事業価値で説明 し、予算→環境改善へと正のスパイラルを作る筋立てが現実的だ。若手の「 まず試す 」を小口の時間・費用枠で支える、 チャレンジを褒める のをマネジメントが率先する――こうした「手触りのある運営」が随所にあった。 三菱電機 OSPO/ISPO:社外の注目を社内の追い風に 三菱電機さんは、 OSPO(OSS)とISPO(InnerSource) を併走させる体制を立ち上げ、 プラットフォームを整える→オープンに載せる という習慣の普及から始めている。社外イベントで注目を集め、 外からの視線を社内の認知に逆流 させる語り口は大企業ならでは。社内イベントの連鎖で 用語が社内の共通語になっていく 過程が披露された。さらに、 11月13日に横浜でカンファレンスをホスト する案内もあり、ムーブメントの「 場 」を先に作る姿勢が印象的だった。 InnerSource Summit 2025 https://innersourcecommons.org/ja/events/isc-2025/ 対談:規程も「みんなで作る」/品質とは「使い手の価値」 対談は、 大企業でインナーソースを実践した現場感 がにじんだ。特に刺さったのは、 開発標準や規程といった「ルール類」こそインナーソースで作る べきだという提案だ。関係者を巻き込み、承認フローまで公開する。 KPIの扱いでは、 金額だけで説明しない 。人数、再利用、レビューのリードタイムなど 金額に寄与する手前の変化 を測る。品質の定義については、 「利用者にとっての価値」 を軸に置き、欠陥数だけで測らない姿勢が示された。生成AIをどう扱うかの問いには、 作り手が人かAIかは本質ではない 、 ユーザー教育と運用設計、フィードバックの回路 まで含めた品質管理が必要だという冷静な回答。 費用配賦や予算の壁をどう越えるかには、 「まず自チームで始め、フォロワーを増やし、制度は後から追随させる」 という、可能なところから現実的に進める作法が共有された。 KDDI「KAGreement」:オープンな合意が文化になる KDDIの登壇は、 「なぜここにいるのか」を言葉にする 取り組み。副社長も参加する 週次のFigJamセッション と Slackでの公開 を通じ、ワーキングアグリーメント(行動指針)を みんなで磨く 。 議論は「 公開・アーカイブ・小さく早く・チーム横断 」といったインナーソースのパターンとよく似た挙動になっている。全社ミーティングで ランダムなブレイクアウト を行い、部門も年次も混ぜて対話する設計は、 組織の「平均値」をじわじわ上げる 実装と言えた。背後では、 エグゼクティブ・スポンサーシップ が確かに支えている。 https://www.docswell.com/s/mitsuba_yu/KLVRX7-2025-09-12-163618 チームラボ:見つからなければ「無い」のと同じ チームラボさんのテーマは、 「使われて育つ」ための仕掛け だ。組織が大きくなるほど、「何がどこにあるのか」が見えない。そこで、 社内の「インナーソース部」 を立ち上げ、興味と仲間の可視化から着手した。 次に作ったのが InnerSource Portal 。リポジトリの概要、オーナー、導入手順、 どのプロジェクトで使われているか 、Issue/PRへの導線を 一枚に集約 する。Issueテンプレートも「質問・改善・機能追加…」と種類を分け、 書きやすさを設計 。 さらに、「 InnerSource Champion 」「最多貢献」「神Issue」「新人賞」など 称号と表彰 を遊び心を持って運用していく構想が語られた。定期のリリース共有会や、期限を切って 全員でひとつの社内OSSを作る日 のような企画も視野に入れている。狙いはただ一つ。 「使いたいと思ったとき、そこにある」 状態を増やすことだ。 https://speakerdeck.com/teamlab/innersource_gathering_tokyo2025_teamlab まとめ:小さな成功体験が文化になる 今年のISGTで響いたのは、どの登壇にも共通していた 「やさしい導線」 でした。 最初のPRが怖くないUI。最初のレビューが心地よい言葉。最初の採用が誇らしくなる称号。 そうした 小さな成功体験の積み重ね が、コミュニティ全体の「平均値」を押し上げ、サイロを溶かしていくのです。インナーソースは制度名ではなく、日々の ふるまいのデザイン であると強く感じました。 おまけ:懇親会の熱気 イベント後にはInnerSource OST(Open Space Technology)が行われ、各テーマごとに分かれてディスカッションが続きました。その流れで自然に懇親会へとつながったため、場は最初から大いに盛り上がり、活発な意見交換が続いていました。「カルチャーをどう良くしていくか」という問いに真剣に取り組む人たちが集まっていたからこそ、ただの交流ではなく、 次につながる対話 が数多く生まれたのが印象的です。
Introduction Hello! This is Matsuo from the Cloud Infrastructure Group. Before I knew it, this August marked the start of my third year with the company. This time, I encountered a minor stumbling block with S3 event notifications, so I decided to put together a brief summary as a way to share what I've learned. I hope this serves as a helpful reference for anyone facing similar issues. How it started 📋 We received a request for a system where a Lambda function uploads a file to S3, and the upload triggers an SQS message. We implemented this by configuring S3 event notifications to send messages to SQS when an ObjectCreated:Put event occurs. However, shortly after configuration, it was pointed out that no messages were arriving in SQS, and we investigated this. What is S3 Event Notification? S3 Event Notification, in simple terms, is an S3 feature that allows you to send event-driven notifications to other AWS services when events occur within an S3 bucket. You can select( Lambda/SNS/SQS ) as the notification destinations. 📝 Additional Notes This time, we use S3 events to send notifications directly to SQS, but notifications via Amazon EventBridge are also possible. When integration with multiple services or complex configurations is required, you should also consider using EventBridge integration. The Initial Hypothesis and Response In fact, the destination SQS queue had been renamed once, and the S3 event notification settings were updated accordingly. Since the requester confirmed immediately after the configuration that there were no issues, we formed the following hypothesis. 💭 Could it be that the SQS or S3 event notification configurations weren't updated due to an internal AWS issue? Therefore, we deleted and recreated the S3 event notification and SQS configuration. After uploading a sample .txt file to S3 using a PUT operation, the event was successfully received. At this point, we thought the problem had been resolved. Unresolved issues and new findings After communicating the results of our investigation and the actions taken, we received a follow-up message from the requester indicating that the problem still remains unresolved. It included other information. 💬 I tried it with a .txt file and received the message. It seems that message reception may not be working properly with .csv files. The requester mentioned above. I wondered what it is. And I checked the contents of S3, and then. I found something! File Extension Size .txt Several bytes .csv Approximately 20 MB After determining that the file size was likely the cause rather than the file extension, we investigated again! Identification of the Cause Upon reviewing the code for the Lambda function handling file uploads, I found S3.upload_file . def upload_file(temp_file_path: str, S3_bucket_name: str, S3_file_name: str): """ ファイルアップロード """ logger.info('---- Upload ----') S3 = boto3.client('s3') res = S3.upload_file(temp_file_path, S3_bucket_name, S3_file_name) logger.info(f"ファイル {S3_file_name} をアップロードしました") The boto3 upload_file method automatically performs multipart uploads when the file size exceeds a certain threshold (8 MB).[^1] This seems to be the cause.🔍 Wrong S3 Event Type The event type configured for S3 was ObjectCreated:Put In this configuration, only creations via ObjectCreated:Put will be notified. However, the event triggered during a multipart upload is ObjectCreated:CompleteMultipartUpload With multipart uploads, a completely different event is triggered, not ObjectCreated:Put ! In the case of large files, the following sequence prevented the event from being triggered: Lambda uploads the file to S3 During this process, the upload_file method automatically performs a multipart upload Consequently, the event generated is ObjectCreated:CompleteMultipartUpload As a result, since the event notification configuration is set only to ObjectCreated:Put , it is not notified to SQS. By the way, what is multipart upload? Multipart upload is a mechanism that divides large files into multiple parts for uploading. Here are the benefits.[^2] High-speed: Upload multiple parts in parallel Reliability: Resend only failed parts Resume interrupted: Resuming from where you left off is possible even during network outages Solution S3 event notification settings change The event type settings have been changed as follows: All object creation events (ObjectCreated:*) With this setting, all of the following events will be subject to notification. ObjectCreated:Put ObjectCreated:Post ObjectCreated:Copy ObjectCreated:CompleteMultipartUpload After changing the settings, we confirmed that S3 event notifications are successfully sent to SQS even for large CSV files! In this case, since it was clear that the file upload source was solely the Lambda function, we set it to ObjectCreated:* (all object creation events). Other solutions are available In addition to modifying S3 event notification settings, you can also handle this by using the put_object method in boto3 code. However, this time we chose to modify the S3 event notification configurations. The reasons: No need to modify the application code Stable even against future changes Other upload methods are also supported. I believe modifying S3 event configurations is the most versatile and reliable approach , but depending on the situation, handling it in code can also be effective. What We've Learned 1. Testing with file size in mind When validating with small test files, multipart uploads may not occur, potentially causing issues to be overlooked. When processing involves S3, it is crucial to conduct testing using file sizes expected in the production environment . 2. Understanding boto3's internal behavior While upload_file method in boto3 is convenient, it may automatically execute multipart uploads internally. You need to understand this behavior and configure the appropriate events. 3. Considerations for event notification configurations For this specific requirement, where the upload source was limited solely to Lambda functions, we selected s3:ObjectCreated:* .However, in general, it is recommended to narrow down the event types to only those that are strictly necessary. The key is to understand what upload method SDKs like boto3 use internally and configure the appropriate events accordingly. Conclusion This issue occurred due to the following factors overlapping. 1. boto3 automatically performed multipart uploads 2. S3 event notifications were configured to target only ObjectCreated:Put events 3. The issue could not be reproduced with small test files Feeling reassured after testing with small files, only to encounter issues in actual operation , is a common pitfall. If you're experiencing similar issues, please check the following: The size of the file uploaded to S3 The event type targeted by your S3 event notification I hope this blog post has been a little helpful. [^1]: boto3 TransferConfig official documentation [^2]: Multipart upload official documentation
はじめに こんにちは! クラウドインフラグループの松尾です。 早いもので今年の8月で入社3年目に突入してしまいました。 今回は、S3イベント通知について、ちょっとした躓きがあったので 知識のアウトプットとして簡単にまとめようと思いました。 同じような問題に直面した方の参考になれば幸いです。 きっかけ 📋LambdaがファイルをS3にアップロードし、アップロードをトリガーとしてSQSにメッセージを送信したい とあるシステムで依頼があり、S3イベント通知を、 ObjectCreated:Put の場合SQSに送信するように設定し実現しました。 しかし、設定後しばらくしてSQSにメッセージが届いていないという指摘があり、その調査を行いました。 S3イベント通知とは? そもそもS3イベント通知とは、ざっくりいうとS3バケット内でイベントが発生した際に イベント駆動で他のAWSサービスに通知を送信できるS3の機能 です。 通知先は( Lambda/SNS/SQS )が選択できます。 📝 補足 今回は直接SQSへ通知するためS3イベントを採用していますが、Amazon EventBridgeを経由した通知も可能です。 複数サービスとの連携や複雑な設定が必要な場合はEventBridge連携も検討する必要があります。 最初の仮説と対応 実は通知先のSQSは一度名前を変更しており、併せてS3イベント通知の設定も更新していました。 設定後すぐに依頼者には確認いただき問題ないと連絡をいただいていたため、以下の仮説を立てました。 💭 SQSやS3イベント通知の設定がAWSの内部的な問題で更新できていなかった可能性があるのではなかろうか? そのためS3イベント通知/SQSを一度削除し、再作成し適当なtxtファイルをS3にPUTしたところ、イベントを正常に受信できました。 この時点では「問題は解決した」と考えました。 問題の未解決と新たな発見 調査及び対応で行ったことを伝え、しばらくすると、 再度依頼者からまだ問題が解決していないという旨の連絡がありました。 その際依頼者から 💬拡張子.txtのファイルで試してみたところ、メッセージ受信しました。.csvでメッセージ受信できるようになっていない可能性がありそう とのメッセージも添えられていました。 おやおや?と思いS3の中身を確認すると、あっ!と思った箇所が! 拡張子 サイズ .txt 数バイト .csv 約20Mバイト 拡張子の問題ではなく ファイルサイズ が原因の可能性が高そうだと判断して再度調査してみました! 原因の特定 ファイルアップロード処理を行っているLambda関数のコードを確認したところ、 S3.upload_file を見つけました。 def upload_file(temp_file_path: str, S3_bucket_name: str, S3_file_name: str): """ ファイルアップロード """ logger.info('---- Upload ----') S3 = boto3.client('s3') res = S3.upload_file(temp_file_path, S3_bucket_name, S3_file_name) logger.info(f"ファイル {S3_file_name} をアップロードしました") boto3の upload_file メソッドは、ファイルサイズが一定の閾値(8Mバイト)を超えると、自動的に マルチパートアップロード を実行します。[^1] 原因はここにありそうです🔍 S3イベントタイプが違った S3の設定イベントタイプとして設定していたのは ObjectCreated:Put この設定では、 ObjectCreated:Put による作成のみが通知対象となります。 しかし、マルチパートアップロードで発生するイベントは ObjectCreated:CompleteMultipartUpload マルチパートアップロードでは、 ObjectCreated:Put とは 全く別のイベント が発生することになります! つまり大きなファイルの場合、このような流れでイベントが起こらなかったのです LambdaがS3にファイルをアップロードする その際、 upload_file メソッドにより、自動的にマルチパートアップロードを実行 そのため発生したイベントは ObjectCreated:CompleteMultipartUpload 結果としてイベント通知設定が ObjectCreated:Put のみだったため、SQSに通知されない ちなみにマルチパートアップロードとは? マルチパートアップロードは、大きなファイルを複数の部分(パート)に分割してアップロードする仕組みです。以下の利点があります。 ^2 高速: 複数パートを並列でアップロード 信頼性: 失敗したパートのみ再送信 中断の再開: ネットワーク障害時でも途中から再開が可能 解決方法 S3イベント通知の設定変更 イベントタイプの設定を以下のように変更しました すべてのオブジェクト作成イベント (ObjectCreated:*) この設定により、以下のすべてのイベントが通知対象となります ObjectCreated:Put ObjectCreated:Post ObjectCreated:Copy ObjectCreated:CompleteMultipartUpload 設定変更後、大きなCSVファイルでもSQSにS3イベント通知が正常に送信されることを確認できました! 今回のケースでは、ファイルのアップロード元がLambda関数のみであることが明確だったため、 ObjectCreated:* (すべてのオブジェクト作成イベント)に設定しました 他の解決方法もある S3イベント通知の設定変更以外にも、boto3のコードでput_objectメソッドを使用することでも対応することは可能です。 ただし、今回はS3イベント通知の設定変更を選択しました。理由は アプリケーションコードを変更する必要がない 将来的な変更に対しても安定している 他のアップロード方法でも対応可能 S3イベント設定変更が最も汎用的で確実 だと思いますが、状況によってはコード側での対応も有効だと思います。 学んだこと 1. ファイルサイズを意識したテスト 小さなテストファイルでの検証では、マルチパートアップロードが発生せず、問題を見落とす可能性があります。S3を挟む処理の場合は 本番環境で想定されるファイルサイズでのテスト を実施することが重要です。 2. boto3の内部動作への理解 boto3の upload_file メソッドは便利ですが、内部でマルチパートアップロードを自動実行する場合があります。 この動作を理解して、適切なイベント設定を行う必要があります。 3. イベント通知設定の考慮点 今回は要件上、アップロード元がLambda関数のみと限定されていたため、 s3:ObjectCreated:* を選択しましたが、一般的には必要最小限のイベントタイプに絞ることが推奨されます。 重要なのは、boto3などのSDKが内部でどのようなアップロード方法を使用するかを把握し、それに応じた適切なイベント設定を行うことです。 まとめ 今回の問題は、以下の要因が重なって発生しました。 1. boto3が自動的にマルチパートアップロードを実行 2. S3イベント通知が`ObjectCreated:Put`イベントのみを対象に設定されていた 3. 小さなテストファイルでは問題が再現されない 小さなファイルでテストして安心していたら、実際の運用で問題が発覚する というのは、よくある落とし穴だと思います。 同様の問題で困っている方は、以下を確認してみてください。 S3にアップロードしたファイルのサイズ S3イベント通知の対象イベントタイプ このブログが少しでも参考になれば幸いです。 [^1]: boto3 TransferConfig公式ドキュメント
Hello! I'm Kasai from the SRE Team. KINTO Technologies Corporation participated in "SRE NEXT 2025" as a Platinum Sponsor! Thank you to everyone who visited our booth! It was inspiring to talk with so many participants, and I gained a lot of valuable insights. You can read about the roundtable discussion held by our members who attended SRE NEXT in the article linked below. Please take a look! https://blog.kinto-technologies.com/posts/2025-07-18-sre_next_look_back/ At our booth, we conducted a survey with the theme: "What's Your NEXT?" Thank you to everyone who took part. In this article, I’d like to share the survey results with you. Sticky notes on the board (left: Day 1, right: Day 2) ![] (/assets/blog/authors/kasai/20250731/20250724_162209.jpeg =400x) Stack of sticky notes for the two days Survey Results Over the two days, we received 312 responses to the survey. I categorized the responses into several themes, which I would like to share here. *The classification was done using Gemini. SRE & Organizational Culture (60 responses) These responses were related to the practice and promotion of SRE, fostering organizational culture, recruitment, and team building. Becoming able to promote SRE Spreading the SRE culture Hiring engineers successfully Creating an engineering organization that excites people! Building a common platform for Embedded SRE Many responses touched on topics such as how to behave as an SRE, how to spread SRE-oriented thinking, and the difficulty of hiring more engineers. I can strongly relate to these points, as I also think about how to effectively communicate the benefits of SRE thinking to other teams and help them recognize its value. AI Utilization (58 responses) These responses were related to improving operational efficiency and creating new value through the use of AI and LLMs. Applying AI to infrastructure and SRE Achieving Agentic DevOps Enabling incident response entirely with AI Reducing toil through AI utilization Achieving a seven-day weekend with AI!! Some participants were already using AI and wanted to expand its use, while others were planning to start using it. I also use generative AI, but only as an aid when writing code. For example, I have not yet reached the point where I can have AI handle all incident responses. I would like to challenge myself to find ways to use it beyond coding, such as in areas like incident response. Technology & Service Improvement (58 responses) These responses were related to improving service quality, including the introduction of SLI/SLO, enhancing performance, and resolving technical debt. Introducing SLI/SLO Significantly improving performance to enhance user experience Resolving technical debt Fully automating operations Working with eBPF Many responses focused on introducing SLI/SLO and expanding observability. I still have much to learn about SLI/SLO, and my experience with implementation is limited, so I would like to continue practicing and building my knowledge and expertise. Business & Career (41 responses) These responses were related to contributing to business, product growth, IPO, career changes, and promotions. Understanding the business side and applying it to work Business Growth IPO Become a CTO Changing jobs In addition to technical topics, there were also responses related to business. I feel that it is necessary to discuss reliability not only with the development side but also with the business side. There were others who shared the same view, as well as those who were thinking about how to grow the business. At present, I still feel somewhat distant from the business side, so I would like to close that gap and contribute to the business from an SRE perspective. Speaking & Output (30 responses) These responses were related to sharing information externally, such as speaking at conferences or writing blog posts. Preparing for a keynote talk Outputting every month Hosting PHP Conference 2026 Hokkaido Submitting many CFPs Becoming a speaker Perhaps because it was a conference setting, there were many responses related to speaking engagements. Some people even wrote that they would speak at SRE NEXT, which made me feel that it’s wonderful we are able to host events that make people want to present. I would also like to spread the word about our company through blog posts and speaking engagements so that more people can learn about who we are. Other (65 responses) These were private goals or unique responses that did not fit into the categories above. Go to a sauna See penguins in Antarctica Be happy Have dinner Go on a trip Some participants also wrote things unrelated to work or SRE. It reminded me that maintaining health and taking time to refresh are also important for doing good work. Since we had the opportunity, we also asked Gemini to put together a summary. Thank you so much for the 312 responses we received. Looking over the results, it is clear that today’s engineers have a healthy, well-rounded set of interests, covering technology, organization, business, and even personal goals. The most common responses were in the "SRE & Organizational Culture" category, which shows that people see SRE not just as a technical role but as part of the culture of the team and the entire organization, and that they are strongly motivated to foster and develop this culture. This was closely followed by "AI Utilization" and "Technology & Service Improvement," indicating a balance between the desire to explore cutting-edge technology and a strong awareness of SRE’s core responsibility of improving service reliability. The fact that these three categories ranked at the top in almost equal numbers symbolizes a very balanced view of engineering. There were also many responses related to "Business & Career" and "Speaking & Output," which was notable because it showed that many people view their role from a broader perspective, going beyond their daily work to contribute to the business and give back to the community. Finally, the "Other" category clearly reflected values that prioritize well-being, including health, personal life, and individual dreams, alongside work. Overall, the survey results were highly insightful, painting a picture of the modern, mature engineer—someone who pursues technical excellence, contributes to the organization, people, and business, and also seeks fulfillment in their personal life. The above are the survey results and summary. The theme of SRE NEXT 2025 is "Talk NEXT." This is why we decided to conduct this survey with a topic related to the theme, with the hope of engaging in a dialogue with people who visited our booth. By choosing a highly abstract topic, we were able to have conversations not only with SREs, but also with engineers in other roles, and even with non-engineers. I feel it turned out to be a very good topic. Many of the responses touched on generative AI, which has been a recent trend, and I got the impression that many people are thinking about how to apply AI in the SRE field. As for myself, while I can collect system metrics, I am still not making full use of that data to support decision-making, influence the business, or contribute to operations. I believe that being able to do so would make my role as an SRE even more interesting, and I would like to take on that challenge. I could relate to many of the other responses as well, and I enjoyed having conversations at the booth on the day. Thank you very much! In Closing Once again, thank you very much to everyone who visited our booth! We were delighted to welcome such a large number of visitors. Many thanks also to the SRE NEXT organizers for putting on a wonderful event. I believe the stamp rally and other activities played a big part in bringing so many people to our booth. I hope to be involved in some way in next year's SRE NEXT as well. See you again at SRE NEXT next year! We Are Hiring! KINTO Technologies is looking for new teammates to join us! We are happy to start with a casual interview. If you’re even a little curious, please apply using the link below! https://hrmos.co/pages/kinto-technologies
KINTOテクノロジーズ(以下KTC)で my route(iOS)を開発しているRyomm( @__ryomm )です。 2025年9月19-21日の3日間にわたって開催されたiOSDC Japan 2025にゴールドスポンサーとして協賛しました✨ 昨年に引き続き、2回目の協賛となります。 1年の間に様々なカンファレンスでスポンサー出展してノウハウを身につけた技術広報の力を借りつつ、iOSアプリ開発に携わるエンジニアが中心となって準備を進めてきました。社内のクリエイティブ室と協力して作成した、こだわりのノベルティやブース企画を紹介します。 「アプリのひみつ アプリ内製開発ストーリー」冊子 こちらはノベルティBOXに封入した特製冊子です。 昨年の反省を踏まえ、今年は紙媒体にしようと決めていました。 しかし、ただのチラシというのも味気なくつまらないと思ったので、絵本のような質感で手に取りたくなる冊子を目指して制作しました。 予算との戦いがありつつも、検討を重ねた末にやわらかい質感の「ヴァンヌーボVG スノーホワイト」という用紙を採用しました。KTCで開発しているアプリのストーリーを楽しく紹介しています。 ぜひご一読いただき、KTCのアプリ開発に触れてみてください。 ブース企画 今回のブースのテーマは「KTCを知ってもらう」でした。 今年度のKTCでは AIファースト が注力テーマのひとつに据えられています。 そこから、案出しのマンダラートにて「VTuberのようなキャラクターが喋っていたらインパクトがあるのではないか?」というアイディアが生まれ、紆余曲折を経てKTCのAI広報「るぴあ」がブースでじゃんけんをすることになりました。 今回るぴあをブースに立たせるにあたって、クリエイティブ室の桃井さん( @momoitter )に全面協力いただきました。 るぴあ誕生秘話についてはこちらの記事をご覧ください。 https://blog.kinto-technologies.com/posts/2025-03-07-creating_a_mascot_with_generative_AI/ るぴあをブースに立たせるにあたって等身大になる大型のサイネージを購入したのですが、会場でストリーミング再生をするのは品質に不安があったためオフラインで動画をシームレスに再生できるようにMacアプリを作成しました。 こちらのMacアプリはヒロヤさん( @TRAsh___ )との共作です。 プロトタイプ版はGemini CLIを利用して1時間半ほどで完成しました。 プロトタイプ版では VideoPlayer に AVPlayer を渡し、AVPlayerItemを差し替えて動画を再生しています。 待機・呼び込み動画はそれぞれ何種類かあり、全てデフォルトポジションで始まり、デフォルトポジションに戻るようにすることで動画をなめらかに繋げられるようにしました。 ただこちらの実装では動画間のチラつきが激しく、シームレスな再生とは言えません。 そこで AVPlayer から AVQueuePlayer に変更し、次の動画まであらかじめキューに詰めておくことで事前にロードし、シームレスに動画が再生されるように改修しました。 じゃんけんモードへの移行/勝ち負け動画への移行等に関してはキューを割り込ませる必要があり、若干のチラつきは発生しますが、その他の部分ではきれいにつながるようになったと思います。 さて、そんなじゃんけんを勝ち抜いた猛者の方々にはトミカ、残念ながら負けてしまった方々にはありがとうめぐリズムをプレゼントしていました。 遊びにきていただいたみなさま、ありがとうございました!またどこかでパワーアップしたるぴあと遊んでくださいね。 https://x.com/KintoTech_Dev/status/1969236820270420032 さらに、じゃんけんだけではコミュニケーションに不安があったため、話のタネとして「iOSアプリ開発で楽しいことは?」というテーマでアンケートも実施していました。 こちらにもたくさんご回答いただき、ありがとうございました! 最後に、参加したKTCのメンバーで記念写真を撮りました📸 登壇情報 弊社から2名登壇しておりました🎉 iPhoneを用いたフライトシム用ヘッドトラッカーの自作事例 by Felix Chon https://fortee.jp/iosdc-japan-2025/proposal/dfe5819b-6eb7-4880-a89e-411a839b794c QRコードの仕様ってn種類あんねん by Ryomm https://fortee.jp/iosdc-japan-2025/proposal/a1ddb24c-ecc2-4db7-8bce-5a002a1489e1 ぜひニコ生タイムシフトやYoutubeからご覧ください。
Hello, this is Hoka winter. For about a year, KINTO Technologies (KTC) has been running the 10X Innovation Culture Program announced by Google Cloud Japan G.K. in September 2023 to foster an innovation-driven organizational environment. This time, aside from the usual 10X, I will talk about the 10x Innovation Culture Pitch practice session we attended. What is the 10x Innovation Culture Pitch Practice Session? The purpose of this training is to develop the facilitation skills necessary to implement the "10X Innovation Culture Program" within your company. To do this, you need a deep understanding of the 10X Innovation Culture Program. This training is designed to deepen that understanding. This was our second time taking part in the training. Last time, the training was mainly attended by managers. Since then, the progress of 10X has been dramatic, so this time, volunteer members—mainly team leaders—took part. The 10x Innovation Culture Pitch practice session is broadly divided into two parts. One part involves “learning the six elements for creating innovation,” and the other focuses on “expressing the six elements in our own words.” ![](/assets/blog/authors/hoka/20250714/image6.png =600x) Preparation for the Training What I've learned about 10X from Google people is that the difficulty level for KTC gradually increases. In the first training session, all KTC employees “merely participated,” but in the second session, KTC employees took on the role of presenters for the culture session. In other words, they play an important role as presenters who convey the six elements for creating innovation to other participants. ![](/assets/blog/authors/hoka/20250714/image3.png =600x) Thankfully, the presentation slides were prepared by the people at Google, so all we at KTC had to do was read out the six elements. Even though it was just that simple, it was incredibly difficult!!! The six elements contain many of Google's ideas and examples of how to be an innovative organization. However, simply reading them will not reach the hearts of participants. We practiced many times until we could speak in our own words, incorporating episodes from KTC and our own experiences. In particular, we remembered the Google presenters from the first training session and focused on speaking confidently and at an easy-to-follow pace. ** On the Day of the Training** The day has finally arrived. 27 people gathered at the Google office in Shibuya. Participants once again joined from Osaka, Nagoya, and Tokyo. The day kicked off with an opening talk by Google’s Kota-san. Many thanks to Kota-san, as always. Next, Kissy, the manager who is the main leader of 10X, shared an encouraging message online from the Nagoya office. Amid an atmosphere of “Huh? What’s starting now? What is this training?” we, the presenters, took turns announcing themes one by one. Can we get the participants to understand 10X? Awacchi presented with an original story, I was overly nervous, Yukiki appeared online, Nabeyan was calm like a teacher, Mizuki gave the best performance on the actual day as usual, and Otake had the composure to make others laugh. Everyone performed their best on this very day (if we do say so ourselves). In the post-event survey, as many as 10 participants chose "The culture session was great.” I was also happy to hear comments like "It was just as great as the last Google presenters" and "The talk flowed so naturally—I could follow everything just by looking at the slides and listening to the presentations." Next was the output session. Each team, consisting of six people plus one Googler, moved to their assigned room, and just like the earlier presenters, each person took turns giving a presentation. It was an intensive output time of 20 minutes × 6 people, totaling 120 minutes. The participants used the same slides that the presenters had used earlier, and each gave a 10-minute presentation. There was a 5-minute preparation time before each presentation. While listening to the presentations, other members filled out feedback sheets with points they liked and points requiring improvement, and then provided feedback after each presentation. ![](/assets/blog/authors/hoka/20250714/image1.png =600x) I was part of Team D, and they were so good that I couldn’t help but wonder, "Did they practice at home?" During the feedback time, we naturally discussed the good points of the presentations, and the discussion became lively. For example, the following comments were made: Speak while summarizing, without being bound by slides or a script. Speak in your own words. Stories of failure tend to resonate with the audience. Be empathetic to the audience and avoid imposing too much of a lecturing tone. Catchy phrases like "Motivation Switch” are effective and make things easy to understand. ![](/assets/blog/authors/hoka/20250714/image7.png =600x) In the post-presentation survey, satisfaction with the program was very high, with an average score of 4.7. The following points were selected as “positive aspects of the program content.”: (n=22, multiple answers allowed) It was good to be able to listen to other participants' presentations: 20 people I was glad to have the opportunity to practice myself: 17 people It was great to receive feedback from others: 21 people Closing After the presentations, we held a wrap-up session in the original seminar room. While I wondered how the other groups were doing, Google people summarized the earlier feedback sheets for us using generative AI, "Gemini." ![](/assets/blog/authors/hoka/20250714/image4.png =600x) I was planning to check the feedback sheets from the other groups later, but they were instantly converted to text via Gemini and shared with everyone on the spot. It truly was a “Feedback is a gift!” moment. Not only did we learn the training content itself, but we also gained a lot of tips on how to be more efficient—such as how to install tools quickly, how to make use of feedback sheets, and how to share information from other groups. Thank you so much to everyone at Google. Looking Ahead Through this training, we found that the high-level 10x Innovation Culture Pitch practice sessions are effective even for non-managerial members. So, we plan to implement them at KTC in FY2025. KTC’s challenge to foster innovation is far from over. ![](/assets/blog/authors/hoka/20250714/image8.png =600x)
Introduction Hello! This is Otaka from the Cloud Security Group in KINTO Technologies. On a daily basis, I work on setting up guardrails for our cloud environments and keeping them safe through monitoring and improvements with CSPM and threat detection. To stay up to date on the latest security trends, I joined the Hardening Designers Conference 2025 . Here's my report from the event. What is the Hardening Project? The Hardening Project is a competitive event aimed at improving practical cybersecurity skills. Participants run vulnerable systems while defending against, recovering from, and improving them in response to external attacks, building real-world incident response skills in the process. What sets it apart is that it evaluates not only technical skills but also overall incident response capabilities, including teamwork, documentation readiness, and the establishment of an operational system. The Hardening Designers Conference 2025 that I participated in this time is a hands-on and conference event themed "invisible divide." It served as preparation for the competitive event in October. Day1 Hands-on Program In the hands-on session, we experienced an attack technique called "Living off the Land." This technique involves attackers abusing legitimate, built-in tools and functions already in a system to gain access and carry out harmful activities. For example, in a Windows environment, they use PowerShell, WMI, etc. to conduct the attack. The key is that they use built-in tools and functions, not files brought in from outside. This makes it hard to tell their activities apart from normal operations, and difficult for security tools to detect. Some of the commands used in the attack were ones I'd relied on back in my days as a system administrator. If a tool or command isn't used in normal operations, disabling it might help. But for those frequently used and hard to turn off, the only real option may be to log and monitor them closely. The workshop was a real eye-opener, showing just how sophisticated server attacks have become, and how blurred the line is between malicious activities and legitimate operations. Day2 & 3 Conference Program On Day 2 and 3, a variety of lightning talks and sessions delved into the theme of "divide" in the context of cybersecurity, with speakers sharing the latest technologies, introducing new initiatives, and hosting lively discussions. In the security field, there are often "divides" between different stakeholders, and these can become obstacles that hinder Hardening (security fortification) efforts. For example, divides like the following often arise in actual operations: Divide between Development, Operations, and Security Sometimes, the focus on implementing features and improving operational efficiency can push security to the back seat. For instance, poor password management or weak account controls can create security vulnerabilities. To avoid this, think of security not as a "restriction" but as part of overall "quality," and make sure security requirements are built in from the very start through security-by-design and shift-left practices. Divide between System Users and Developers/Operators While system users desire ease of use, they may not fully grasp the importance of security. Engineers, on the other hand, often find themselves caught in the middle, trying to balance user requests for features with the need to keep the system secure. To bridge this gap, it is necessary to educate users and maintain careful communication with them during system development and operation, fostering their understanding of security. Divide between Rule Makers and Implementers Security personnel who set rules often draw on a range of guidelines from public bodies and specialist organizations to define ideal baselines and rules. However, for those on the ground such as system development and operations teams, system constraints and operational workload can make it hard to implement them as intended. To put this into practice, it is important to take into account constraints and operational loads and take a flexible approach so that security can be implemented properly. Divide between Attackers and Defenders While attackers use technological innovation and teamwork to launch increasingly sophisticated attacks, defenders can end up reacting too slowly because of costs or a lack of understanding among stakeholders. Companies hit by cyberattacks are often reluctant to disclose details, which means valuable knowledge that could prevent similar incidents is not shared in many cases. The defense side would also like to strengthen information sharing and cooperation, but things are not going as smoothly as expected. Divide between AI and Humans Efforts to utilize generative AI are spreading in the IT field, from writing program code to upgrading SOC operations. But in reality, AI often can't take security into account without clear and specific instructions, . Generative AI has come a long way, yet there still seems to be a gap between what humans and AI can do. To utilize AI properly, we still need human know-how, like designing effective prompts and setting up guardrails. When we think about it again, we can see just how many kinds of divides exist. I had never looked at security from this angle before, so this was genuinely insightful. Overcoming the Divides—KINTO Technologies' Approach At the Cloud Security Group, our basic policy is "security for business," and we believe that security should accelerate business operations, not slow them down. We work on security from the following two key angles: Preventative guardrails: We provide security preset account environments with the minimum required security settings already implemented before handing them to development teams. This helps support secure design from the very beginning. Detective guardrails: We use SOC monitoring with tools such as Sysdig, AWS Security Hub, and Amazon GuardDuty to detect and respond to threats in real time. Through regular Posture management, we also conduct kaizen activities to improve problematic configurations. Through these security measures and operations, we are working to create an environment where developers can focus on their work with peace of mind, while adhering to our company's security guidelines and ensuring the necessary security. In short, this is an effort to bridge the divide between rule-makers and implementers, and between development, operations, and security teams. We have also begun taking gradual steps toward AI security (see details here ). However, with technology and trends evolving so rapidly, it feels as though we are currently a little on the back foot. Within the company, the use of generative AI in business operations and its implementation into products are advancing actively, and the challenge ahead will be determining how to implement effective controls while overcoming the divide with AI. Furthermore, we are working to review our mindset toward system development projects at KINTO Technologies, drawing on the IPA's key points for requirement clarification with a house-building analogy as a reference. This is not limited to system construction; from a security perspective as well, it is an effort to be mindful of the divide between system users and engineers, and to foster better relationships and results. For more information about IPA house-building, please see here (in Japanese) . Summary Through the Hardening Designers Conference 2025, I had the valuable opportunity to learn about security trends from the perspective of divide, something I had not consciously considered before. By looking at my own organization's security through the same lens of divide, I was also able to reaffirm our current initiatives. Going forward, I hope to continue and refine our efforts to overcome divides and achieve better security. Lastly Our Cloud Security Group is looking for people to work with us. We welcome not only those with hands-on experience in cloud security but also those who may not have experience but have a keen interest in the field. Please feel free to contact us. For additional details, please check here (in Japanese).