在第二十五天,你已經掌握了 Retrofit 這個專業的網路連線工具。你現在已經具備了 App 開發最核心的幾大技能。
今天,我們就要將這些技能串聯起來,完成一個完整的 App。我們將打造一個簡單的 天氣 App,它能夠:
MVVM 架構,讓程式碼更清晰。TextView 上。MVVM 架構:將網路連線和資料處理的邏輯,與畫面顯示分開。build.gradle 中新增函式庫如果你還沒有加入 Retrofit 和 Gson,請先在 build.gradle (Module: app) 檔案的 dependencies 區塊中加入它們。
`dependencies {
// ... 其他 dependencies ...
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}`
我們需要一個類別來接收從天氣 API 回傳的 JSON 資料。這個 API 的資料結構會比較複雜,通常需要多個類別來對應。
main、weather)。你需要定義好每個盒子裡的東西。建立一個新的 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; }
}`
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,它會將 cityName 和 apiKey 作為參數,加到 URL 的後面,例如:...?q=Taipei&appid=...。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("網路連線失敗");
}
});
}
}`
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);
});
}
}`
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>`
WeatherViewModel.java 的 API_KEY 變數中。今天我們成功地將 MVVM、Retrofit、LiveData 和 Gson 整合在一起,完成了一個具備完整功能的 App!你現在已經具備了打造一個可以從網路取得資料,並優雅地顯示在畫面上的能力。
接下來的幾天,我們將會進入最後的階段:效能優化與進階議題。我們會學習如何讓 App 運行得更順暢,以及處理一些在實際開發中會遇到的問題。
明天見!