iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Mobile Development

Android 開發者養成計畫:從程式邏輯到作品集實戰系列 第 26

Day26- 綜合挑戰:打造一個簡單的天氣

  • 分享至 

  • xImage
  •  

在第二十五天,你已經掌握了 Retrofit 這個專業的網路連線工具。你現在已經具備了 App 開發最核心的幾大技能。

今天,我們就要將這些技能串聯起來,完成一個完整的 App。我們將打造一個簡單的 天氣 App,它能夠:

  1. 從網路 API 取得天氣資料。
  2. 將天氣資料顯示在畫面上。
  3. 使用 MVVM 架構,讓程式碼更清晰。

專案目標

  • 從網路取得資料:我們會使用一個公開的天氣 API,來獲取簡單的天氣資訊。
  • 處理 JSON 資料:我們會將取得的 JSON 資料,轉換成我們的 Java 物件。
  • 顯示在畫面上:將天氣資訊(例如:溫度、天氣狀況)顯示在一個 TextView 上。
  • 使用 MVVM 架構:將網路連線和資料處理的邏輯,與畫面顯示分開。

程式碼實作:打造你的天氣 App

1. 在 build.gradle 中新增函式庫

如果你還沒有加入 RetrofitGson,請先在 build.gradle (Module: app) 檔案的 dependencies 區塊中加入它們。

`dependencies {
    // ... 其他 dependencies ...
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}`

2. 建立資料類別

我們需要一個類別來接收從天氣 API 回傳的 JSON 資料。這個 API 的資料結構會比較複雜,通常需要多個類別來對應。

  • 簡單比喻:這就像是你在收到一個包裹(JSON),裡面還包著幾個小盒子(mainweather)。你需要定義好每個盒子裡的東西。

建立一個新的 Java 類別,取名為 WeatherResponse

`import com.google.gson.annotations.SerializedName;

public class WeatherResponse {
    @SerializedName("main")
    private Main main;
    @SerializedName("weather")
    private Weather[] weather;
    @SerializedName("name")
    private String name;

    public Main getMain() { return main; }
    public Weather[] getWeather() { return weather; }
    public String getName() { return name; }
}

class Main {
    @SerializedName("temp")
    private float temp;

    public float getTemp() { return temp; }
}

class Weather {
    @SerializedName("description")
    private String description;

    public String getDescription() { return description; }
}`

3. 建立 API 介面 (Retrofit)

我們需要一個介面來定義網路請求。

  • 右鍵點擊 MainActivity.java 所在的資料夾,建立一個新的 interface,取名為 WeatherApi
`import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface WeatherApi {
    @GET("data/2.5/weather")
    Call<WeatherResponse> getWeather(
        @Query("q") String cityName,
        @Query("appid") String apiKey
    );
}`
  • 程式碼解釋@Query 註解告訴 Retrofit,它會將 cityNameapiKey 作為參數,加到 URL 的後面,例如:...?q=Taipei&appid=...

4. 建立 ViewModel

這是 MVVM 架構的核心。ViewModel 會處理網路連線的邏輯,並使用 LiveData 來更新介面。

  • 右鍵點擊 MainActivity.java 所在的資料夾,建立一個新的 class,取名為 WeatherViewModel,並繼承 ViewModel
`import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class WeatherViewModel extends ViewModel {

    private final MutableLiveData<String> _weatherData = new MutableLiveData<>();
    public LiveData<String> weatherData = _weatherData;

    private static final String API_KEY = "你的OpenWeather API Key"; // 替換成你的 API Key
    private static final String BASE_URL = "https://api.openweathermap.org/";

    public void fetchWeather(String cityName) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        WeatherApi weatherApi = retrofit.create(WeatherApi.class);
        Call<WeatherResponse> call = weatherApi.getWeather(cityName, API_KEY);

        call.enqueue(new Callback<WeatherResponse>() {
            @Override
            public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
                if (response.isSuccessful() && response.body() != null) {
                    WeatherResponse weatherResponse = response.body();
                    String description = weatherResponse.getWeather()[0].getDescription();
                    float temp = weatherResponse.getMain().getTemp();
                    String message = String.format("地點:%s\n溫度:%s°C\n天氣:%s",
                            weatherResponse.getName(), String.format("%.2f", temp - 273.15), description);
                    _weatherData.setValue(message);
                } else {
                    _weatherData.setValue("查無天氣資訊或API錯誤");
                }
            }

            @Override
            public void onFailure(Call<WeatherResponse> call, Throwable t) {
                _weatherData.setValue("網路連線失敗");
            }
        });
    }
}`

5. 修改 MainActivity.java (View)

Activity 只需要負責顯示畫面,並將使用者輸入傳給 ViewModel

`import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.lifecycle.ViewModelProvider;

public class MainActivity extends AppCompatActivity {

    private WeatherViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText cityInput = findViewById(R.id.cityInput);
        Button fetchButton = findViewById(R.id.fetchButton);
        TextView resultTextView = findViewById(R.id.resultTextView);

        viewModel = new ViewModelProvider(this).get(WeatherViewModel.class);

        fetchButton.setOnClickListener(v -> {
            String city = cityInput.getText().toString();
            if (!city.isEmpty()) {
                viewModel.fetchWeather(city);
            }
        });

        // 觀察 ViewModel 的 LiveData
        viewModel.weatherData.observe(this, result -> {
            resultTextView.setText(result);
        });
    }
}`

6. 修改 activity_main.xml

我們需要 EditText 來讓使用者輸入城市。

`<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/cityInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入城市名稱 (例如:Taipei)" />

    <Button
        android:id="@+id/fetchButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="查詢天氣" />

    <TextView
        android:id="@+id/resultTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="結果將顯示在這裡"
        android:textSize="20sp" />

</LinearLayout>`
  • 注意:你需要去 OpenWeatherMap 網站註冊一個免費帳號,取得你的 API Key,並替換到 WeatherViewModel.javaAPI_KEY 變數中。

今日總結

今天我們成功地將 MVVMRetrofitLiveDataGson 整合在一起,完成了一個具備完整功能的 App!你現在已經具備了打造一個可以從網路取得資料,並優雅地顯示在畫面上的能力。

接下來的幾天,我們將會進入最後的階段:效能優化與進階議題。我們會學習如何讓 App 運行得更順暢,以及處理一些在實際開發中會遇到的問題。

明天見!


上一篇
Day25- Retrofit 函式庫
下一篇
Day27- RecyclerView 效能優化與進階應用
系列文
Android 開發者養成計畫:從程式邏輯到作品集實戰30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言