[TTS] AzureでTTS APIを使う手順

1. サブスクリプションをupgrade

画面左上の「リソースの作成」または上部の検索バーで「Speech」と入力して検索します。
検索結果から「Speech」または「Azure AI サービス」を選択し、「作成」をクリックします。

サブスクリプション 課金が行われるアカウントです。 従量課金制にアップグレード済みのサブスクリプションを選択します。
リソース グループ 先ほど作成したグループです。 作成済みのリソースグループを選択します。
リージョン サービスがデプロイされる場所です。 アプリケーションのユーザーに近いリージョン、または性能・遅延が適切なリージョンを選択します。(例: East US、Japan Eastなど)
名前 このSpeechリソースの名称です。 任意の名前(例: MyTtsResource2025)を入力します。
価格レベル 料金プランです。 Standard (S0) を選択します。(無料枠を使い切っているため)

import azure.cognitiveservices.speech as speechsdk

# Azure Speech Service のキーとリージョン
speech_key = "YOUR_SPEECH_KEY"
service_region = "eastasia"   # East Asia リージョン

# SpeechConfig を作成
speech_config = speechsdk.SpeechConfig(subscription=speech_key, region=service_region)

# 出力音声を設定(標準はwav)
audio_config = speechsdk.audio.AudioOutputConfig(filename="output.wav")

# 音声の種類(例: 日本語の女性)
speech_config.speech_synthesis_voice_name = "ja-JP-NanamiNeural"

# Speech Synthesizer を作成
synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)

# 読み上げるテキスト
text = "こんにちは、これは Azure Text to Speech のテストです。"

# TTS 実行
result = synthesizer.speak_text_async(text).get()

# 結果チェック
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
    print("✅ 音声ファイル 'output.wav' を生成しました")
else:
    print("❌ エラー:", result.reason)

Azureだと音声が全然違いますね!

[LLM] Hugging FaceのSpaceにuploadする

Hugging Face Spaces に Llama / Mistral のチャットデモを公開する最小構成

import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
import torch

# ここを Llama / Mistral など好きなモデルに変更
MODEL_NAME = "mistralai/Mistral-7B-Instruct-v0.2"
# MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"  # ← Llama に変更したい場合

# モデルとトークナイザのロード
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    torch_dtype=torch.float16,
    device_map="auto"
)

def chat_fn(message, history):
    # 過去履歴を LLM のプロンプト形式に変換
    prompt = ""
    for user, assistant in history:
        prompt += f"<s>[ユーザー]: {user}\n[アシスタント]: {assistant}</s>\n"
    prompt += f"<s>[ユーザー]: {message}\n[アシスタント]:"

    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

    output_ids = model.generate(
        **inputs,
        max_new_tokens=200,
        temperature=0.7,
        do_sample=True,
        top_p=0.9
    )

    response = tokenizer.decode(output_ids[0], skip_special_tokens=True)

    # 最後のアシスタント発言だけ抽出
    if "[アシスタント]:" in response:
        response = response.split("[アシスタント]:")[-1].strip()

    history.append((message, response))
    return response, history


# Gradio UI
with gr.Blocks() as demo:
    gr.Markdown("# 🦙💬 Simple Llama / Mistral Chatbot")
    chatbot = gr.Chatbot()
    msg = gr.Textbox(label="Message")

    def user_send(user_message, chat_history):
        return "", chat_history + [[user_message, None]]

    msg.submit(user_send, [msg, chatbot], [msg, chatbot]).then(
        chat_fn, [msg, chatbot], [chatbot]
    )

demo.launch()

===== Application Startup at 2025-11-16 11:06:22 =====

tokenizer_config.json: 0%| | 0.00/2.10k [00:00

[LLM] Hugging Face で出来ること

Hugging Face は オープンソースLLMのプラットフォーム & コミュニティ
世界中のAIモデル・データセット・サンプルコードが集まるGitHub+App Store+AI研究コミュニティ

## Hugging Faceで何が出来る?
🤖 モデルHub Llama・Mistral・Gemma など数万のLLMをダウンロードして試せる
📚 データセットHub 研究用の巨大データセットが公開されていて利用可能
🚀 Transformers ライブラリ Python でLLMを簡単に使える神ライブラリ
🧪 Inference API / Endpoints GPUいらずでモデルをAPIとして利用できる
🧱 PEFT / LoRA サポート 軽量ファインチューニングが簡単
📊 Spaces(デモアプリ) Streamlit / Gradio でAIアプリをホスティング
🫂 AIコミュニティ モデル公開や議論・PRが活発で世界最大級

① Model Hub
世界中のLLMがここに集まっています。

例:
meta-llama/Llama-3.1-8B
mistralai/Mistral-7B
google/gemma-2-9b
EleutherAI/gpt-j-6b
GitHubと同じように、モデルごとにページがあり

説明
推奨ハイパーパラメータ
サンプルコード
重み(weights)
が公開されています。
→ とりあえず Hugging Face の中心はコレ と覚えればOK。

e.g.
https://huggingface.co/meta-llama/Llama-3.1-8B

### Transformer library

from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct")
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct")

inputs = tokenizer("こんにちは!自己紹介してください。", return_tensors="pt")
outputs = model.generate(**inputs, max_length=100)

print(tokenizer.decode(outputs[0], skip_special_tokens=True))

### Space(AIアプリ)
Gradio や Streamlit を使って、ブラウザで動くデモアプリを無料で公開できるサービス。

Mistral (ミストラル)
Mistralは、フランスのAIスタートアップ企業Mistral AIによって開発されているLLMのシリーズです。
開発元: Mistral AI (フランス)
主な特徴:
オープンソース/オープンウェイト: 多くのモデルがオープンソースまたはオープンウェイト(モデルの重み/パラメータが公開されている)で提供されており、研究者や開発者が自由に利用、改変、再配布しやすいのが大きな特徴です。

### Gradio、Streamlit
GradioとStreamlitは、どちらもPythonのコードのみで、機械学習モデルのデモやデータ分析のためのインタラクティブなWebアプリケーションを簡単に作成するためのライブラリ(フレームワーク)です。

Hugging Faceは、特にGradioの開発元を傘下に収めており、そのプラットフォーム「Hugging Face Spaces」でGradioとStreamlitのアプリケーションをホスティング(公開)できるようにしています。

[TTS] リアルタイムLipsyncの実現方法

– TTS APIから得られるVisemeデータとVRMモデルがあれば、リップシンクデモをリアルタイムで実現できる
– Rhubarbで生成されたJSON(Visemeデータ)を、TTS APIがリアルタイムで出力するVisemeデータに置き換えることで、既存のThree.js/VRMのロジックを応用できる

### TTS API連携の場合(実現したいこと)
– TTS APIからのVisemeイベントストリーム
– 再生時間と**Visemeイベントのoffset**を比較し、Viseme IDに対応するVRMブレンドシェイプを適用する.
– TTS API固有のViseme IDをVRMのブレンドシェイプ名 (A, I, U, E, O, NEUTRAL) にマッピングする.

1. TTS API呼び出しとデータ取得
テキスト入力: ユーザーがテキストを入力するためのUI(textareaやinput)と、API呼び出し用のボタンを追加します。
API接続: 選択したTTS API(例: Azure Neural TTS)のエンドポイントに、入力テキストを含むリクエストを送信するJavaScriptコードを実装します.
データ受信: APIから返される合成音声データとVisemeイベントデータ(タイムスタンプ付きの口形情報)を同時に受け取るロジックを構築します.

2. リアルタイム再生とVisemeキューの処理
音声再生: 受信した音声データをWeb Audio APIでリアルタイムにデコード・再生します。
Visemeキューリストの作成: TTS APIから受け取ったVisemeイベントを、あなたのlipsyncData.mouthCuesと同様の形式(start時刻とvalue)のキューリストとしてメモリに保持します。

3. updateLipsync()関数の調整
あなたの既存のupdateLipsync()関数はそのまま使えますが、RhubarbのViseme ID(A, B, C, …)ではなく、TTS APIが使用するViseme IDに合わせてmouthShapeMapのキーを更新する必要があります.
例: Azure TTSはviseme id=”1″(唇を閉じている状態)、viseme id=”2″(Aの音)などの数値IDを使う場合があります.

[LLM] 世界を揺るがす中国発の高性能LLM! DeepSeekとQwenの衝撃

## はじめに:AIの勢力図が変わる?

大規模言語モデル(LLM)の世界は、これまでOpenAIの**GPT**やGoogleの**Gemini**といったアメリカの巨大テック企業が牽引してきました。しかし今、この勢力図を大きく塗り替えようとしているのが、中国から登場した二大巨頭、**DeepSeek**と**Qwen(通義千問)**です。

両モデルは、国際的なベンチマークでトップクラスの性能を発揮するだけでなく、その**提供戦略**においても大きな特徴を持っています。本記事では、この二つの高性能LLMの特徴と、従来のGPT・Geminiとの決定的な違いを解説します。

## 🚀 DeepSeekとは?:効率と推論力のスペシャリスト

DeepSeekは、中国のAIスタートアップによって開発されました。その最大の特徴は、**「効率性」**と**「高性能な推論能力」**の両立です。

### 💡 主な特徴

* **低コストでの開発:** 大量のデータと計算リソースを必要とするLLM開発において、DeepSeekは**非常に効率的な学習プロセス**を採用していることで知られています。
* **推論能力の特化:** 特に**コーディング(プログラミング)**や**数学**といった論理的な推論を要するタスクで、非常に高いスコアを叩き出します。
* **MoE(Mixture-of-Experts)の活用:** 効率的なモデル構造であるMoEアーキテクチャを採用し、より高速な推論と少ない計算コストでの運用を実現しています。

## 🌟 Qwen(通義千問)とは?:アリババ発の多言語対応モデル

Qwen(通義千問)は、中国の巨大テック企業**Alibaba Cloud(アリババクラウド)**によって開発されました。商用利用を強く意識しており、グローバルな展開を視野に入れています。

### 💡 主な特徴

* **驚異的な多言語対応:** **日本語を含む119言語以上**に対応する強力な多言語処理能力を持ち、世界中のユーザーやビジネスでの利用を想定しています。
* **長いコンテキストウィンドウ:** **長文の理解・処理**に優れており、大量のドキュメントを一度に処理する能力が高いです。
* **マルチモーダル機能:** テキストだけでなく、画像や音声なども理解できる**マルチモーダル機能**も強化されており、より複雑なタスクに対応できます。

## 🆚 DeepSeek・QwenとGPT・Geminiの決定的な違い

DeepSeekとQwenが、GPTやGeminiと最も大きく異なる点は、その**モデル提供戦略**と**利用のしやすさ**にあります。

### 1. オープンソース戦略

| モデル群 | 提供形態の傾向 | 開発・利用のしやすさ |
| :— | :— | :— |
| **GPT / Gemini** | 主に**クローズドソース**(API経由での提供が中心) | 高度な機能は提供されますが、モデルの内部構造は非公開で、**カスタマイズに制限**がある。 |
| **DeepSeek / Qwen** | 多くの高性能モデルを**オープンソース**として公開 | モデルを**無料でダウンロード**し、自由に**カスタマイズ**して商用利用が可能。スタートアップや研究機関が導入しやすい。 |

このオープンソース戦略により、DeepSeekとQwenは世界の開発者コミュニティに急速に浸透し、LLMの**コモディティ化(汎用化)**を加速させています。

### 2. コストとアクセシビリティ

オープンソース版のDeepSeekやQwenは、利用にAPI料金などがかからないため、**コストを大幅に抑える**ことができます。これは、資金力の限られたスタートアップや、AI導入の実験段階にある企業にとって非常に大きなメリットです。

### 3. 特化分野

| モデル | 強みとする分野 |
| :— | :— |
| **GPT** | 汎用的な対話、創造性、幅広い知識。 |
| **Gemini** | Google検索との連携による最新情報、マルチモーダル(画像、動画の理解)。 |
| **DeepSeek** | **コーディング**、**数学**、**効率的な推論**。 |
| **Qwen** | **多言語対応**、**長文処理**、アリババクラウドとの連携。 |

## 📌 まとめ:AIの未来は多様化へ

DeepSeekとQwenの登場は、AIの高性能化が特定の地域や企業に独占される時代が終わりつつあることを示しています。

* **コストを抑えて独自のAIを開発したい**
* **特定のタスク(例:コーディングや多言語対応)に特化したモデルが欲しい**

というニーズを持つ企業や開発者にとって、DeepSeekとQwenはGPTやGeminiの**強力な代替選択肢**となります。

AIの未来は、特定のモデルに依存するのではなく、それぞれの目的に応じて最適なモデルを選択する**多様化**の時代へと移行していると言えるでしょう。

**あなたもぜひ、DeepSeekやQwenのオープンソースモデルを試して、その力を体験してみてはいかがでしょうか。**

[Dify] LLMからの回答をstreaming-modeにする

response_modeをblockingからstreamingにする
requests.postの呼び出しでstream=Trueを設定。これにより、レスポンス全体がダウンロードされるのを待たずに、すぐにチャンクの読み込みを開始する

import requests
import json
import os
import time

from dotenv import load_dotenv
load_dotenv()

# --- 設定(変更なし) ---
API_KEY = os.environ.get("DIFY_API_KEY")
APP_ID = os.environ.get("APP_ID")

# BASE_URLはDify Cloud (dify.ai) の場合です。セルフホストの場合は適宜変更してください。
BASE_URL = "https://api.dify.ai/v1" 
CHAT_ENDPOINT = f"{BASE_URL}/chat-messages"

user_input = "おはようございます。今日の天気と、何か面白いニュースがあれば教えてください。"

# --- 1. ペイロードの変更 ---
payload = {
    "query": user_input,
    "inputs": {
        "context": "Null" 
    },
    "user": "user_python_script_001", 
    # ストリーミングを有効にする
    "response_mode": "streaming", 
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

print("✅ ワークフローの出力 (ストリーミング回答):")
print("-" * 30)

try:
    # --- 2. API呼び出しの変更 (stream=True) ---
    response = requests.post(
        CHAT_ENDPOINT,
        headers=headers,
        data=json.dumps(payload),
        stream=True  # ストリーミング応答を有効にする
    )
    
    response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる

    # --- 3. 応答の処理ロジックの変更 ---
    # iter_lines() でチャンクごとに処理
    for line in response.iter_lines():
        if line:
            # SSE形式の行をデコード
            decoded_line = line.decode('utf-8')
            
            # SSEのデータ行は 'data: ' で始まる
            if decoded_line.startswith('data: '):
                # 'data: ' の部分を削除し、JSONとしてパース
                json_str = decoded_line[6:].strip()
                try:
                    data = json.loads(json_str)
                except json.JSONDecodeError:
                    # JSONデコードエラーはスキップまたはログに記録
                    continue

                # タイプに応じて処理
                event_type = data.get('event')
                
                # 'message' イベントが回答のテキストチャンクを含む
                if event_type == 'message':
                    # 'answer' フィールドにテキストの断片が含まれる
                    text_chunk = data.get('answer', '')
                    
                    # ターミナルにテキストを逐次出力(flush=Trueで即時表示)
                    print(text_chunk, end='', flush=True)

                # 'message_end' イベントは回答の終了を示す
                elif event_type == 'message_end':
                    # 処理終了後、改行してループを抜ける
                    print("\n" + "-" * 30)
                    print("✅ ストリーミング終了")
                    break

except requests.exceptions.HTTPError as errh:
    print(f"\nHTTP エラーが発生しました: {errh}")
    # エラーが発生した場合、response.text を表示(非ストリームとして再取得が必要な場合あり)
    # ストリームモードでエラーがすぐに発生しないこともあるため、これはシンプルなエラー表示
    try:
        print(response.json())
    except:
        print(response.text)
except requests.exceptions.RequestException as err:
    print(f"\nリクエスト中にエラーが発生しました: {err}")
finally:
    # 接続が成功した場合でも、エラーで中断した場合でも、最後に改行してプロンプトをきれいに表示
    print()

なるほど、レスポンスモードが変わるのね。了解!

[Figma/Design] Figma でのタイポグラフィ基礎

🎯 UIデザインに向いているフォントの特徴
クセが少ない(可読性が高い)
太字・中太・通常など ウェイトの種類が多い
大きさや太さを変えても破綻しない

👍 よく使われる無料フォント
● Noto Sans / Noto Sans JP(Google)
→ 一番無難。日本語も英語も揃う。

● Inter
→ グローバルで最も使われるUIフォントの1つ。
→ Figma公式フォントにも入っている。

● Roboto / Roboto Condensed
→ Android系UIで多用される。

● SF Pro(Mac)
→ iOS / macOSで使用。

2. 階層(Typographic Hierarchy)
タイポの階層とは、ユーザーが画面を見た時に
どこが重要で、どこが補足なのかが一目で分かるようにする仕組み です。

典型的な階層構造(UI用)
役割 例 サイズ(px) 重さ(Weight)
H1(大見出し) 画面タイトル 24–32 Bold / SemiBold
H2(中見出し) セクションタイトル 20–24 Medium / SemiBold
Body(本文) 説明文 14–16 Regular / Medium
Caption(補足) ラベル/注釈 12–13 Regular

3. 行間(Line Height)
行間は文章の可読性に直結する

UIデザインでの基本値は:

● 本文(14–16px)

➡ Line height:120%〜160%(1.2〜1.6)

● 見出し(20px以上)

➡ Line height:110%〜130%

Figmaでの設定方法

テキストを選択 → 右の Line height に数値入力
例:

16px フォント → Line height 24px(=150%)

4. 文字間(Letter spacing)
文字間は、視認性や雰囲気を整えるために使います。
UIの一般指標
本文(14–16px):0〜1%
字幕・小さい文字(12px前後):1~3%
大きい見出し:0%以下でも良い(−1% など)

Figmaでは
Text → Letter spacing
で数値を入力(% か px)できます。

5. 日本語タイポの注意点

✓ 日本語は欧文より“詰まりやすい”ため
→ 行間は少し広め(150%前後)がおすすめ。

✓ フォントの種類によっては縦方向の見え方が違う
→ 見出しは Noto Sans JP Bold
→ 本文は Noto Sans JP Regular
など役割でフォントウェイトを分ける。

✓ UIでは明朝体が読みにくいので避ける(特別な用途を除く)

なるほど、全然意識していなかったけど、見やすくするのですね。納得です。

[Android/iOS] ストア申請の流れ

以下に Android(Google Play)と iOS(App Store) のストア申請の流れをわかりやすくまとめ ました。
「開発者登録 → 審査 → 公開」まで、現場で実際に行う手順ベースです。

📱 Android(Google Play)ストア申請の流れ
✅ 1. Google Play デベロッパー登録
Google アカウントで登録
登録料:25ドル(買い切り)

✅ 2. Google Play Console にアプリを作成
アプリ名入力
パッケージ名(com.example.app)が必要
アプリ種別(アプリ / ゲーム)
無料 / 有料の選択

✅ 3. アプリの情報入力
以下を必ず入力する必要があります:
ストア掲載情報
アプリ名
短い説明
長い説明
アイコン
スクリーンショット
プロモーション画像
Appカテゴリ(ジャンル・コンテンツレーティング)
プライバシーポリシーURL
アプリのアクセス権に関する申請(位置情報・カメラ等)

✅ 4. App Bundle(AAB)をアップロード
Android Studio → Build > Generate Signed App Bundle
keystore で署名
AAB を Play Console にアップロード

✅ 5. 審査リクエスト(リリース)
プロダクションリリースへ申請
審査期間:数時間~3日程度
新人アカウントほど長くなる傾向あり

✅ 6. 公開
公開後、数時間でストアに反映される
ホットフィックスは数時間で更新可能

🍎 iOS(App Store)ストア申請の流れ
✅ 1. Apple Developer Program 登録
料金:年間 99ドル
個人 / 企業で登録形態が異なる
iPhone実機テストには Developer Program が必要

✅ 2. App Store Connect でアプリ作成
アプリ名
バンドルID(Xcode側設定と一致)
SKU の設定
カテゴリ選択

✅ 3. アプリの情報入力(詳細)
App Store用の説明情報
タイトル
サブタイトル
プロモーションテキスト
スクリーンショット(6.7インチ / 5.5インチ 必須)
アイコン(1024×1024)
年齢レーティング
プライバシーポリシーURL
データ収集とトラッキングの申告(AppTrackingTransparency)

✅ 4. Xcode でビルドしてアップロード
Xcode → Archive → Distribute App
Transporter(Apple純正)経由でもOK
Upload 後、App Store Connect の「TestFlight」に表示される

✅ 5. App Review に提出
審査に必要な入力:
デモアカウント(ログイン必要アプリの場合)
審査用メモ
有料アイテムの申請(In-App Purchaseがある場合)
審査期間:1~3日程度(早ければ半日)

✅ 6. リリース(公開)
審査OK → 公開日を即時または手動で選択
公開後、数時間で App Store へ反映

🔍 Android と iOS の比較(重要ポイント)
項目 Android iOS
開発者登録 25ドル(買い切り) 99ドル/年
審査 数時間〜3日 半日〜3日
ビルド形式 AAB IPA(Xcodeからアップロード)
審査の厳しさ やや緩い かなり厳格
更新反映 早い やや遅い
🎯 まとめ(全

[android] figmaのデザインのandroidへの落とし方

1. figmaから以下の情報を取得する
🎨 色(Color)
#RRGGBB または RGBA
不透明度(Opacity)
グラデーション

🔤 文字(Typography)
フォント名
サイズ(sp)
行間(lineHeight)
Font Weight

🔲 余白(Spacing)
padding / margin(dp)
コンポーネントの width/height

📐 角丸(Corner Radius)
dp

⭐ シャドウ(Elevation / shadow)
shadow の offset / blur / color

2.ザイントークンとして Android へ落とし込む
Figma の色・文字・spacing 情報をそのままコードに書くのではなく、
“テーマ” として一箇所にまとめる のがプロのやり方

Jetpack Compose のテーマ例(Theme.kt)

object AppColors {
    val Primary = Color(0xFF4CAF50)
    val Secondary = Color(0xFF03A9F4)
    val TextPrimary = Color(0xFF333333)
}

object AppTypography {
    val Title = TextStyle(
        fontSize = 20.sp,
        fontWeight = FontWeight.Bold
    )
    val Body = TextStyle(
        fontSize = 14.sp
    )
}

object AppRadius {
    val Medium = 12.dp
}

object AppSpacing {
    val Small = 8.dp
    val Medium = 16.dp
}

3.Compose の UI 実装(Figma → Compose への変換)
Figma のボタンを例にします。

🎨 Figma のボタン例
幅:200
高さ:48
角丸:12
背景色:#4CAF50

テキスト:白、16sp、Medium

@Composable
fun AppButton(text: String, onClick: () -> Unit) {
    Box(
        modifier = Modifier
            .size(width = 200.dp, height = 48.dp)
            .background(AppColors.Primary, shape = RoundedCornerShape(AppRadius.Medium))
            .clickable { onClick() },
        contentAlignment = Alignment.Center
    ) {
        Text(text, style = TextStyle(color = Color.White, fontSize = 16.sp))
    }
}

これをcomposeとして使う

<Button
    android:layout_width="200dp"
    android:layout_height="48dp"
    android:background="@drawable/rounded_button"
    android:text="Button"
    android:textSize="16sp"
    android:textColor="@android:color/white" />

[android] Dependency Injection(Hilt)

Hilt を使って依存性(Repository)を注入する最小例
Activity から Repository を直接 new しない → テストしやすい設計

MyRepositoy.kt

interface MyRepository {
    fun getMessage(): String
}

class MyRepositoryImpl : MyRepository {
    override fun getMessage() = "Hello from Repository!"
}

AppModule.kt

package com.example.hiltsample

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideMyRepository(): MyRepository = MyRepositoryImpl()
}

MainActivity.kt

// MainActivity.kt
package com.example.hiltsample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    @Inject lateinit var repository: MyRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            // DI された repository が使える
            androidx.compose.material3.Text(text = repository.getMessage())
        }
    }
}

Activity が Repository に依存しない(疎結合)
FakeRepository に差し替えるだけで テストが簡単
Repository の作り方を 1 箇所にまとめられて 保守性が上がる