5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Tauriで楽天レシピ取得アプリ作ってみた

Last updated at Posted at 2023-09-26

はじめに

今回はTauriを使ってレシピ取得アプリを作成しました。
著者はRustもReact(TypeScript)も初学者のため学習のために作成しました。

完成形のGitHubリポジトリ
https://github.com/taiyou1116/tauri-recipe-search

作るモチベ

RustとTypeScriptを一度に学べる!!
Rustの非同期処理について学べる!!

使う技術

  • Tauri
  • Rust
  • React (TypeScript)

対象者

この記事は下記のような人を対象にしています。

  • Tauriでデスクトップアプリを作成してみたい方
  • Rust, TypeScript初学者
  • 楽天APIを使ってみたい方

完成形

recipe_img_01.png
recipe_img_02.png

追記(2023/10/11)

下記コードでevalメソッドを使用していますが、直接jsコードを実行してしまうので、代わりにemitメソッドを使用しましょう。emitは引数の関数をイベントに登録できます。

以前のコード

commands.rs
// メイン関数で受信と処理
    while let Some(recipes) = rx.recv().await {
        // 重複を見てあげる
        let mut new_recipes = Vec::new();
        for recipe in recipes {
            if seen.insert(recipe.recipe_id.clone()) {
                new_recipes.push(recipe);
            }
        }

        // ここで受信したレシピを処理
        let js_command = format!(
            "window.receiveRecipes({})",
            serde_json::to_string(&new_recipes).unwrap()
        );
        window.eval(&js_command).unwrap();
    }

次のように変更

commands.rs
// メイン関数で受信と処理
    while let Some(recipes) = rx.recv().await {
        // 重複を見てあげる
        let mut new_recipes = Vec::new();
        for recipe in recipes {
            if seen.insert(recipe.recipe_id.clone()) {
                new_recipes.push(recipe);
            }
        }
        window.emit("receive_recipes", &new_recipes).unwrap();
    }

その他詳細は私のGitHubや、下のコメントしてくれた方を参考にしてください。↓

コード解説

問題点

get_category_dataでレシピデータを取得しますが、楽天APIは人気のレシピデータを4つずつしか取得できません。また、APIへのリクエストは1秒ほど間隔を空けないとエラーが発生してしまいます。(負荷を抑えるため)

tokio::spawnで解決

tokio::spawnを使用して新しくタスクを作成します。その中でAPIへのリクエストをしレシピを取得します。
取得するたびにwhile let Some(recipes) = rx.recv().awaitの部分で受け取ります。
そして、window.eval(&js_command).unwrap();でフロントのwindow.receiveRecipesを実行します。

commands.rs
#[tauri::command(async)]
pub async fn get_category_data(category_name: String, window: Window) -> Result<(), String> {
    let (tx, mut rx) = mpsc::channel(32); // 32はバッファサイズ

    tokio::spawn(async move {
        let config = config::Config::new();
        let response: ApiResponseOfCategory = reqwest::get(&config.category_url)
            .await
            .unwrap()
            .json()
            .await
            .unwrap();

        let categories = extract_categories_from_response(&response, &category_name);

        for category in categories {
            let recipe_url = format!("{}{}", config.recipe_url, category.category_id);
            let response: ApiResponseOfRecipe = reqwest::get(&recipe_url)
                .await
                .unwrap()
                .json()
                .await
                .unwrap();

            // チャネルを通じてレシピを送信
            if let Err(_) = tx.send(response.result).await {
                return;
            }

            // 1秒待機
            sleep(Duration::from_secs(1)).await;
        }
    });

    let mut seen = HashSet::new();

    // メイン関数で受信と処理
    while let Some(recipes) = rx.recv().await {
        // 重複を見てあげる
        let mut new_recipes = Vec::new();
        for recipe in recipes {
            if seen.insert(recipe.recipe_id.clone()) {
                new_recipes.push(recipe);
            }
        }

        // ここで受信したレシピを処理
        let js_command = format!(
            "window.receiveRecipes({})",
            serde_json::to_string(&new_recipes).unwrap()
        );
        window.eval(&js_command).unwrap();
    }

    Ok(())
}

window.receiveRecipes

ここではrecipeListにRust側で取得したレシピを格納しています。

App.tsx
useEffect(() => {
    window.receiveRecipes = function(recipes) {
      useStore.setState((state) => ({
        recipeList: [...state.recipeList, ...recipes],
      }));
      
      onGetDataMatchingMaterial(materialName);
    };

    // クリーンアップ関数:コンポーネントのアンマウント時に実行
    return () => {
      window.receiveRecipes = null;
    };
  }, [materialName]);

window.recipeRecipes

window.recipeRecipesを使うためには、WindowというグローバルインターフェースにreceiveRecipesを追加する必要があります。

global.d.ts
import { Recipe } from "./Recipe";

declare global {
  interface Window {
    receiveRecipes: ((recipes: Recipe[]) => void) | null;
  }
}

おわりに

Tauriで楽天レシピを取得するアプリについて、レシピ取得部分のみ解説してみました。
コード全体についてはGitHubリポジトリをご覧ください。

参考記事

楽天APIを使って人気レシピを取得してみた
Pythonで楽天レシピAPIからレシピを取得する

5
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?