如何使用 ViewModel 和 LiveData 进行改造 API 调用 [英] How to make retrofit API call using ViewModel and LiveData
问题描述
这是我第一次尝试实现 MVVM 架构,我对进行 API 调用的正确方法有点困惑.
目前,我只是想从 IGDB API 进行一个简单的查询,并输出日志中第一项的名称.
我的活动设置如下:
public class PopularGamesActivity 扩展 AppCompatActivity {@覆盖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>() {@覆盖public void onChanged(@Nullable List 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";私有静态最终 int LIMIT = 30;私有 LiveData>mGameList;public PopularGamesViewModel(@NonNull 应用程序) {超级(应用程序);//创建改造构建器Retrofit.Builder builder = new Retrofit.Builder().baseUrl(igdbBaseUrl).addConverterFactory(GsonConverterFactory.create());//构建改造改造改造 = builder.build();//创建改造客户端RetrofitClient 客户端 = retrofit.create(RetrofitClient.class);调用<LiveData<List<Game>>>call = client.getGame(FIELDS,命令,限制);call.enqueue(new Callback<LiveData<List<Game>>>(){@覆盖public void onResponse(Call<LiveData<List<Game>>>call, Response<LiveData<List<Game>>>响应){如果(response.body()!= null){Timber.d("调用响应体不为空");mGameList = response.body();} 别的 {Timber.d("调用响应体为空");}}@覆盖public void onFailure(Call<LiveData<List<Game>>> call, Throwable t) {Timber.d("改造调用失败");}});}public LiveData<List<Game>>获取游戏列表(){返回 mGameList;}
现在的问题是因为这是一个API调用,mGameList
的初始值为null,直到call.enqueue
返回一个值.这将导致
popViewModel.getGameList().observe(this, new Observer>() {
- 那么处理 LiveData 观察的正确方法是什么?在进行 API 调用时?
- 我是否执行了 Retrofit API 调用在正确的地方?
您的代码中有 3 个问题.
- 您必须创建一个
MutableLiveData
对象,因为在 API 调用之前您有一个空响应,然后您的LiveData
对象将通过 IGDB 响应以某种方式填充.
private MutableLiveData>mGameList = new MutableLiveData();//...public LiveData<List<Game>>获取游戏列表(){返回 mGameList;}
- 另一个错误是改变了
mGameList
的引用而不是设置它的值,所以尝试改变:
Timber.d("调用响应体不为空");mGameList = response.body();
到
mGameList.setValue(response.body());
- 在
ViewModel
类中调用改造可以避免关注点分离.最好创建一个存储库模块并通过接口获得您的响应.阅读这篇文章了解详情.
<块引用>
Repository 模块负责处理数据操作.他们为应用程序的其余部分提供干净的 API.他们知道从哪里得到数据来自以及更新数据时进行哪些 API 调用.你可以将它们视为不同数据源之间的中介(持久性模型、网络服务、缓存等).
this is the first time I'm trying to implement MVVM architecture, and I'm a bit confused about the correct way to make an API call.
Currently, I'm just trying to make a simple query from the IGDB API, and output the name of the first item in a log.
My activity is setup as follow:
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);
}
});
}
}
My View Model is set up as follow:
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;
}
Now the problem is because this is an API call, the initial value of mGameList
will be null, until call.enqueue
returns with a value. This will cause a null pointer exception with
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
- So what is the correct way to handle the observation of a LiveData, while API call is being made?
- Did I perform the Retrofit API call in the right place?
There are 3 problems in your code.
- You must create a
MutableLiveData
object because you have an empty response before API call then yourLiveData
object will be filled somehow through the IGDB response.
private MutableLiveData<List<Game>> mGameList = new MutableLiveData();
//...
public LiveData<List<Game>> getGameList() {
return mGameList;
}
- Another mistake is changing the reference of
mGameList
instead of setting its value, So try to change:
Timber.d("Call response body not null");
mGameList = response.body();
to
mGameList.setValue(response.body());
- Calling retrofit inside your
ViewModel
class is avoiding separation of concerns. It's better to create a repository module and get your response through an interface. Read this article for details.
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.).
这篇关于如何使用 ViewModel 和 LiveData 进行改造 API 调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!