如何使用 ViewModel 和 LiveData 进行改造 API 调用
How to make retrofit API call using ViewModel and LiveData
这是我第一次尝试实现 MVVM 架构,我对进行 API 调用的正确方法有点困惑。
目前,我只是想从 IGDB API 进行简单查询,并在日志中输出第一项的名称。
我的activity设置如下:
public class PopularGamesActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popular_games);
PopularGamesViewModel popViewModel = ViewModelProviders.of(this).get(PopularGamesViewModel.class);
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
@Override
public void onChanged(@Nullable List<Game> gameList) {
String firstName = gameList.get(0).getName();
Timber.d(firstName);
}
});
}
}
我的视图模型设置如下:
public class PopularGamesViewModel extends AndroidViewModel {
private static final String igdbBaseUrl = "https://api-endpoint.igdb.com/";
private static final String FIELDS = "id,name,genres,cover,popularity";
private static final String ORDER = "popularity:desc";
private static final int LIMIT = 30;
private LiveData<List<Game>> mGameList;
public PopularGamesViewModel(@NonNull Application application) {
super(application);
// Create the retrofit builder
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(igdbBaseUrl)
.addConverterFactory(GsonConverterFactory.create());
// Build retrofit
Retrofit retrofit = builder.build();
// Create the retrofit client
RetrofitClient client = retrofit.create(RetrofitClient.class);
Call<LiveData<List<Game>>> call = client.getGame(FIELDS,
ORDER,
LIMIT);
call.enqueue(new Callback<LiveData<List<Game>>>() {
@Override
public void onResponse(Call<LiveData<List<Game>>> call, Response<LiveData<List<Game>>> response) {
if (response.body() != null) {
Timber.d("Call response body not null");
mGameList = response.body();
} else {
Timber.d("Call response body is null");
}
}
@Override
public void onFailure(Call<LiveData<List<Game>>> call, Throwable t) {
Timber.d("Retrofit call failed");
}
});
}
public LiveData<List<Game>> getGameList() {
return mGameList;
}
现在的问题是因为这是一个API调用,mGameList
的初始值将是null,直到call.enqueue
returns有一个值。这将导致
的空指针异常
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
- 那么处理LiveData观察的正确方法是什么,
当 API 正在通话时?
- 我是否执行了 Retrofit API 调用
在正确的地方?
你的代码有 3 个问题。
- 您必须创建一个
MutableLiveData
对象,因为在 API 调用之前您的响应为空,然后您的 LiveData
对象将通过 IGDB 响应以某种方式填充。
private MutableLiveData<List<Game>> mGameList = new MutableLiveData();
//...
public LiveData<List<Game>> getGameList() {
return mGameList;
}
- 另一个错误是更改了
mGameList
的引用而不是设置它的值,所以尝试更改:
Timber.d("Call response body not null");
mGameList = response.body();
至
mGameList.setValue(response.body());
- 在您的
ViewModel
class 中调用改造可以避免关注点分离。最好创建一个存储库模块并通过接口获取您的响应。阅读此 article 了解详情。
Repository modules are responsible for handling data operations. They
provide a clean API to the rest of the app. They know where to get the
data from and what API calls to make when data is updated. You can
consider them as mediators between different data sources (persistent
model, web service, cache, etc.).
刚刚受Google的Demo启发,创建了一个库,可以为Retrofit添加LiveData支持。用法很简单:
Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.build()
.create(GithubService::class.java)
.getUser("shawnlinboy").observe(this,
Observer { response ->
when (response) {
is ApiSuccessResponse -> {
//success response
}
else -> {
//failed response
}
}
})
图书馆网站:
https://github.com/shawnlinboy/retrofit-livedata-adapter
//改造的示例界面
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<String>> listRepos(@Path("user") String user);
}
// 视图模型 class
public static class MainActivityViewModel extends AndroidViewModel {
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
final GitHubService gitHubService = retrofit.create(GitHubService.class);
// binding to EditText in DataBinding layout
public final MutableLiveData<String> userName = new MutableLiveData<>();
// binding to ListView in DataBinding layout
public final LiveData<List<String>> listLiveData = Transformations.switchMap(userName, (s) -> new MutableLiveData<List<String>>() {
{
if (!TextUtils.isEmpty(s)) {
gitHubService.listRepos(s).enqueue(new Callback<List<String>>() {
@Override
public void onResponse(Call<List<String>> call, Response<List<String>> response) {
setValue(response.body());
}
@Override
public void onFailure(Call<List<String>> call, Throwable t) {
}
});
}
}
});
public MainActivityViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
super(application);
}
}
这是我第一次尝试实现 MVVM 架构,我对进行 API 调用的正确方法有点困惑。
目前,我只是想从 IGDB API 进行简单查询,并在日志中输出第一项的名称。
我的activity设置如下:
public class PopularGamesActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popular_games);
PopularGamesViewModel popViewModel = ViewModelProviders.of(this).get(PopularGamesViewModel.class);
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
@Override
public void onChanged(@Nullable List<Game> gameList) {
String firstName = gameList.get(0).getName();
Timber.d(firstName);
}
});
}
}
我的视图模型设置如下:
public class PopularGamesViewModel extends AndroidViewModel {
private static final String igdbBaseUrl = "https://api-endpoint.igdb.com/";
private static final String FIELDS = "id,name,genres,cover,popularity";
private static final String ORDER = "popularity:desc";
private static final int LIMIT = 30;
private LiveData<List<Game>> mGameList;
public PopularGamesViewModel(@NonNull Application application) {
super(application);
// Create the retrofit builder
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(igdbBaseUrl)
.addConverterFactory(GsonConverterFactory.create());
// Build retrofit
Retrofit retrofit = builder.build();
// Create the retrofit client
RetrofitClient client = retrofit.create(RetrofitClient.class);
Call<LiveData<List<Game>>> call = client.getGame(FIELDS,
ORDER,
LIMIT);
call.enqueue(new Callback<LiveData<List<Game>>>() {
@Override
public void onResponse(Call<LiveData<List<Game>>> call, Response<LiveData<List<Game>>> response) {
if (response.body() != null) {
Timber.d("Call response body not null");
mGameList = response.body();
} else {
Timber.d("Call response body is null");
}
}
@Override
public void onFailure(Call<LiveData<List<Game>>> call, Throwable t) {
Timber.d("Retrofit call failed");
}
});
}
public LiveData<List<Game>> getGameList() {
return mGameList;
}
现在的问题是因为这是一个API调用,mGameList
的初始值将是null,直到call.enqueue
returns有一个值。这将导致
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
- 那么处理LiveData观察的正确方法是什么, 当 API 正在通话时?
- 我是否执行了 Retrofit API 调用 在正确的地方?
你的代码有 3 个问题。
- 您必须创建一个
MutableLiveData
对象,因为在 API 调用之前您的响应为空,然后您的LiveData
对象将通过 IGDB 响应以某种方式填充。
private MutableLiveData<List<Game>> mGameList = new MutableLiveData();
//...
public LiveData<List<Game>> getGameList() {
return mGameList;
}
- 另一个错误是更改了
mGameList
的引用而不是设置它的值,所以尝试更改:
Timber.d("Call response body not null");
mGameList = response.body();
至
mGameList.setValue(response.body());
- 在您的
ViewModel
class 中调用改造可以避免关注点分离。最好创建一个存储库模块并通过接口获取您的响应。阅读此 article 了解详情。
Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.).
刚刚受Google的Demo启发,创建了一个库,可以为Retrofit添加LiveData支持。用法很简单:
Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.build()
.create(GithubService::class.java)
.getUser("shawnlinboy").observe(this,
Observer { response ->
when (response) {
is ApiSuccessResponse -> {
//success response
}
else -> {
//failed response
}
}
})
图书馆网站: https://github.com/shawnlinboy/retrofit-livedata-adapter
//改造的示例界面
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<String>> listRepos(@Path("user") String user);
}
// 视图模型 class
public static class MainActivityViewModel extends AndroidViewModel {
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
final GitHubService gitHubService = retrofit.create(GitHubService.class);
// binding to EditText in DataBinding layout
public final MutableLiveData<String> userName = new MutableLiveData<>();
// binding to ListView in DataBinding layout
public final LiveData<List<String>> listLiveData = Transformations.switchMap(userName, (s) -> new MutableLiveData<List<String>>() {
{
if (!TextUtils.isEmpty(s)) {
gitHubService.listRepos(s).enqueue(new Callback<List<String>>() {
@Override
public void onResponse(Call<List<String>> call, Response<List<String>> response) {
setValue(response.body());
}
@Override
public void onFailure(Call<List<String>> call, Throwable t) {
}
});
}
}
});
public MainActivityViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
super(application);
}
}