如何从 api 获取数据并将数据插入 Android Studio 中的 ROOM(数据库)?
Posted
技术标签:
【中文标题】如何从 api 获取数据并将数据插入 Android Studio 中的 ROOM(数据库)?【英文标题】:how to fetch data from api and insert the data to ROOM (database) in Android Studio? 【发布时间】:2021-10-06 16:16:24 【问题描述】:我的代码运行良好,但每次人们打开硬币活动时,从 coinmarketcap api(名称、符号名称、价格、体积和市值)加载 500 个硬币大约需要 5 秒。我使用facebook shimmer
让人们知道正在加载某些内容,但 5 秒是很多,因为该应用程序在 1 到 2 秒内加载了更多繁重的内容,例如带有图像的 wordpress 数据。但是,这 500 个硬币在 5 秒内并不酷……这是我的代码……
public void getCoinList()
//posts = 500
ApiInterface apiInterfaceCoin5 =
APIClientCoin.getClient().create(ApiInterface.class);
Map<String, String> params = new HashMap<>();
params.put("limit", posts+"");
Call<CryptoList> call5 = apiInterfaceCoin5.doGetUserListAll(params);
call5.enqueue(new Callback<CryptoList>()
@Override
public void onResponse(Call<CryptoList> call, Response<CryptoList> response)
shimmerFrameLayout.stopShimmer();
shimmerFrameLayout.setVisibility(View.GONE);
swipeRefreshLayout5.setRefreshing(false);
int beforeCoinSize = cryptoList5.size();
CryptoList list5 = response.body();
cryptoList5.addAll(list5.getData());
recyclerView4.setAdapter(adapterCoin5);
@Override
public void onFailure(Call<CryptoList> call, Throwable t)
//Toast.makeText(CryptoListAllCoinActivity.this, "onFailure",
Toast.LENGTH_SHORT).show();
// Log.d("XXXX", t.getLocalizedMessage());
call.cancel();
progressBar3.setVisibility(View.GONE);
shimmerFrameLayout.setVisibility(View.GONE);
swipeRefreshLayout5.setRefreshing(false);
);
@Headers("X-CMC_PRO_API_KEY: HIDDEN")
@GET("/v1/cryptocurrency/listings/latest")
Call<CryptoList> doGetUserListAll(@QueryMap Map<String, String> params);
适配器:
// Involves populating data into the item through holder
// binds the data to the TextView in each row
@Override
public void onBindViewHolder(ViewHolder holder, int
position)
// Get the data model based on position
Datum datum = mData.get(position);
//load coin icon
//if the link doesn't work then you have to upload it into
your own server
Glide.with(context)
.load(new
StringBuilder
("https://s2.coinmarketcap.com/static/img/coins/64x64/")
.append(datum.getId())
.append(".png").toString())
.placeholder(R.drawable.money_icon).into(holder.coin_icon);
Glide.with(context)
.load(new
StringBuilder
("https://s3.coinmarketcap.com/generated/
sparklines/web/7d/usd/")
.append(datum.getId())
.append(".png").toString())
.placeholder(R.drawable.line_24)
.into(holder.sparkline);
TextView symbolName = holder.symbolName;
symbolName.setText(datum.getSymbol());
// Set item views based on your views and data model
TextView name = holder.name;
name.setText(datum.getName());
TextView price = holder.price;
TextView priceDetails = holder.priceDetails;
TextView marketCap = holder.marketCap;
marketCap.setText("$" +
formatNumber(datum.getQuote().getUSD().getMarketCap()));
ImageView coin_icon = holder.coin_icon;
ImageView sparkline = holder.sparkline;
if(datum.getQuote().getUSD().getPrice() >= 1)
price.setText("$" +
formatNumber(datum.getQuote().getUSD().getPrice()));
else
price.setText("$" + String.format("%f",
datum.getQuote().getUSD().getPrice()));
TextView textView24h = holder.textView24h;
textView24h.setText(String.format("%.2f",
datum.getQuote().getUSD().getPercentChange24h()) + "%");
if(datum.getQuote().getUSD().getPercentChange24h() <0.000).
//red
textView24h.setTextColor(Color.parseColor("#EA3943"));
arrowImage.setImageResource(R.drawable.arrow_down);
sparkline.setColorFilter(Color.RED,
PorterDuff.Mode.MULTIPLY);
sparkline.setImageResource(R.drawable.btc_spike);
//changeImageColor(context, sparkline,000);
else
//green
textView24h.setTextColor(Color.parseColor("#18C784"));
arrowImage.setImageResource(R.drawable.arrow_up);
sparkline.setColorFilter(Color.GREEN,
PorterDuff.Mode.MULTIPLY);
sparkline.setImageResource(R.drawable.btc_spike);
Glide.with(context)
.load(new
StringBuilder
("https://s2.coinmarketcap.com/static/img/coins/64x64/")
.append(datum.getId())
.append(".png").toString())
.placeholder(R.drawable.money_icon)
.into(holder.coin_icon);
更新
现在我已经设置了数据库,但不知道如何将数据插入数据库...例如: 我在哪里添加这个:
AppDatabase db =
AppDatabase.getDbInstance(this.getApplicationContext());
//coin object
db.coinDao().insertAllCoins();
在 onResponse 里面?以及如何?
2021 年 8 月 5 日更新:
AppDatabase.class
@Database(entities = Coins.class, version = 1)
public abstract class AppDatabase extends RoomDatabase
public abstract CoinDao coinDao();
private static AppDatabase INSTANCE;
public static AppDatabase getDbInstance(Context context)
if(INSTANCE == null)
INSTANCE = Room
.databaseBuilder(context.getApplicationContext(),
AppDatabase.class,"DB_COIN")
.allowMainThreadQueries()
.build();
return INSTANCE;
币道
@Dao
public interface CoinDao
@Query("SELECT * FROM coin_table")
List<Coins> getAll();
@Insert
void insertAllCoins(Coins... coins);
@Delete
void delete(Coins coins);
@Update
void updateUsers(Coins... coins);
Coins.java
@Entity(tableName = "coin_table")
public class Coins
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "symbol")
public String symbol;
@ColumnInfo(name = "slug")
public String slug;
@ColumnInfo(name = "circulating_supply")
public Double circulatingSupply;
@ColumnInfo(name = "total_supply")
public Double totalSupply;
@ColumnInfo(name = "max_supply")
public Double maxSupply;
@ColumnInfo(name = "date_added")
public String dateAdded;
@ColumnInfo(name = "num_market_pairs")
public Integer numMarketPairs;
@ColumnInfo(name = "cmc_rank")
public Integer cmcRank;
@ColumnInfo(name = "coin_symbol")
public String lastName;
@ColumnInfo(name = "last_updated")
public String lastUpdated;
@ColumnInfo(name = "price")
public Double price;
@ColumnInfo(name = "volume_24h")
public Double volume24h;
@ColumnInfo(name = "percent_change_1h")
public Double percentChange1h;
@ColumnInfo(name = "percent_change_24h")
public Double percentChange24h;
@ColumnInfo(name = "percent_change_7d")
public Double percentChange7d;
@ColumnInfo(name = "market_cap")
public Double marketCap;
2021 年 8 月 6 日更新 基于 Muhammad Shuja 的回答 对于 CoinRepository,我收到此错误:
但如果我将其更改为 CriptoList,那么它会说它需要一个 List,哈哈……知道为什么吗?
请注意,我使用带有 s 的 Coins,因为那是类名。
如果我把它改成 CriptoList 它会说这个
coinDao.insertAll(response.body());
需要一个列表
我有我的public getCoinList()
一点供参考,以了解我目前如何从 api 获取数据。
而且,是的,我想每 1 次更新一次数据……就像我想进行一次 api 调用并每 1 分钟更新一次数据一样。谢谢ssssssssssssss
【问题讨论】:
我建议在完成不同步骤时添加一个计时器和记录时间,这样您就可以缩小搜索范围,然后可以用您的发现更新您的问题,以便我们为您提供帮助轻松 我经常使用外部 API,我通常避免在用户交互时直接访问 API 服务器(除非它不是获取 - 即发布、删除、更新等)。我通常在后台以不同的时间间隔运行一堆作业,并将数据存储在某个地方(数据库、文件等),然后通过我自己的 API 使其可用。当您有 500 个用户同时登录并同时访问市值服务器时,您会很快意识到一次获取数据、存储数据并根据需要随时访问数据更有意义。自己的服务器。 @HanletEscaño 是的,我很乐意这样做,你能提供一些代码吗?另外,你能告诉我数据库中的数据将如何更新吗?就像...它会每x次调用一次api?或者是其他东西。另外,我想获得 3,000 多个硬币......所以你的解决方案可以救我 @Maduro 我无法提供代码,但概念很简单。对于我拥有的每项工作,我将响应保存在我的数据库中的方式与我得到它的方式完全相同(尽管我通常将 JSON 序列化/反序列化为对象,然后将它们移动到我的数据库)。然后,我不是每次用户需要这些数据时都调用 API,而是直接从我的数据库中获取数据。您更新数据的频率取决于您和您的需求。我的作业需要从每 5 秒运行一次到每天一次到每月一次,这真的取决于。 听起来棒极了!你能至少发一个教程吗?我只是在寻找一些关键字,看看你需要什么...... 【参考方案1】:创建一个CoinRepository
并在那里实现您的逻辑。如果 DB 为空或需要刷新数据,让您的 repo 从 API 中查询数据并将其插入 DB,然后在需要时从 DB 中获取。
查看this codelab 以实际了解 Room。
创建Coin
实体,保持实体/模型类名称单数和它们各自的表名复数是一个好习惯。
Coin.java
@Entity(tableName = "coins")
public class Coin
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "symbol")
public String symbol;
@ColumnInfo(name = "slug")
public String slug;
@ColumnInfo(name = "circulating_supply")
public Double circulatingSupply;
@ColumnInfo(name = "total_supply")
public Double totalSupply;
@ColumnInfo(name = "max_supply")
public Double maxSupply;
@ColumnInfo(name = "date_added")
public String dateAdded;
@ColumnInfo(name = "num_market_pairs")
public Integer numMarketPairs;
@ColumnInfo(name = "cmc_rank")
public Integer cmcRank;
@ColumnInfo(name = "coin_symbol")
public String lastName;
@ColumnInfo(name = "last_updated")
public String lastUpdated;
@ColumnInfo(name = "price")
public Double price;
@ColumnInfo(name = "volume_24h")
public Double volume24h;
@ColumnInfo(name = "percent_change_1h")
public Double percentChange1h;
@ColumnInfo(name = "percent_change_24h")
public Double percentChange24h;
@ColumnInfo(name = "percent_change_7d")
public Double percentChange7d;
@ColumnInfo(name = "market_cap")
public Double marketCap;
创建CoinDao
接口并在其中添加您的查询方法,我添加了get(id)和update(coin)等其他方法,您可以根据需要使用它们。
CoinDao.java
@Dao
public interface CoinDao
@Query("SELECT * FROM coins")
LiveData<List<Coin>> getAll();
@Query("SELECT * FROM coins WHERE id = :id")
LiveData<Coin> get(int id);
@Insert
void insertAll(List<Coin> coins);
@Update
public void update(Coin coin);
@Query("DELETE FROM coins")
void deleteAll();
按如下方式创建您的数据库类:
DB.java
@Database(entities = Coin.class, version = 1)
public abstract class DB extends RoomDatabase
private static final String TAG = "DB";
// DB INFO
private static final String databaseName = "coin_database";
// DAOs
public abstract CoinDao coinDao();
// INSTANCE
private static volatile DB INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
public static DB getInstance(final Context context)
if (INSTANCE == null)
synchronized (DB.class)
if (INSTANCE == null)
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), DB.class, databaseName)
.build();
Log.d(TAG, "New instance created...");
return INSTANCE;
创建 CoinRepository
并在其中添加您的 WebAPI/DB 逻辑。请注意,我已将您的 API 调用移至 getCoins() 方法中,您需要根据您的逻辑对其进行修改。确保您的呼叫响应返回 List<Coin>
,否则您必须自己从 CryptoList
创建一个 List<Coin>
。
CoinRepository.java
public class CoinRepository
private static final String TAG = "Repo/Coin";
private final CoinDao coinDao;
private final LiveData<List<Coin>> coins;
public CoinRepository(Application application)
coinDao = DB.getInstance(application).coinDao();
coins = coinDao.getAll();
Log.d(TAG, "New instance created...");
/*
I've added boolean flag to check if data reload from Web API is compulsory.
You can remove this flag and modify it as per your logic.
DataReadyListener is a callback listener.
Its onDataReady() method gets fired when data is ready.
*/
public void getCoins(boolean reload, DataReadyListener listener)
if(reload)
//Modify this portion as per your logic
ApiInterface apiInterfaceCoin5 =
APIClientCoin.getClient().create(ApiInterface.class);
Map<String, String> params = new HashMap<>();
params.put("limit", posts+"");
apiInterfaceCoin5.doGetUserListAll(params)
.enqueue(new Callback<List<Coin>>()
@Override
public void onResponse(Call<List<Coin>> call, Response<List<Coin>> response)
Future<?> future = DB.databaseWriteExecutor.submit(() ->
coinDao.deleteAll(); //remove this if you want to keep previous data
coinDao.insertAll(response.body());
Log.d(TAG, "Data inserted in \"coins\" table");
);
try
future.get();
if (future.isDone())
listener.onDataReady(coins);
catch (ExecutionException e)
e.printStackTrace();
catch (InterruptedException e)
e.printStackTrace();
@Override
public void onFailure(Call<List<Coin>> call, Throwable t)
//Handle failure here
);
else
listener.onDataReady(coins);
public LiveData<Coin> getCoin(int id)
return coinDao.get(id);
public interface DataReadyListener
void onDataReady(LiveData<List<Coin>> coins);
最后创建CoinViewModel
。你可以完全跳过这一步,直接从CoinRepository
查询数据。但是ViewModel
有自己的优点,详情请查看docs。
CoinViewModel.java
public class CoinViewModel extends androidViewModel
private CoinRepository repository;
public CoinViewModel(@NonNull Application application)
super(application);
repository = new CoinRepository(application);
public void getCoins(boolean reload, CoinRepository.DataReadyListener listener)
repository.getCoins(reload, listener);
public LiveData<Coin> getCoin(int id)
return repository.getCoin(id);
现在在你的Activity/Fragment中使用CoinViewModel
,首先初始化它:
用户界面(活动/片段)
private CoinViewModel coinViewModel;
@Override
protected void onCreate(Bundle savedInstanceState)
....
coinViewModel = new ViewModelProvider(this).get(CoinViewModel.class);
....
最后使用您的 ViewModel 来查询数据。如果您想从 Web API 重新加载数据,请将第一个参数发送为 true
,如果您想重用 DB 中的现有数据,请保留标志为 false
:
coinViewModel.getCoins(true, new CoinRepository.DataReadyListener()
@Override
public void onDataReady(LiveData<List<Coin>> coins)
//Add or set data in adapter
//for setData() you need to create setData() method in adapter
//which first clears existing data and then adds new data to list and notifies about dataset change.
coins.observe(this, coinsList -> coinsAdapter.setData(coinsList));
);
希望对您有所帮助。
2021 年 7 月 8 日更新
在与 OP 讨论后,我更新了 CoinRepository
代码。现在,它不再依赖布尔标志,而是使用 RxAndroid
在 1 分钟间隔后自动更新数据:
public class CoinRepository
private static final String TAG = "Repo/Coin";
private final CoinDao coinDao;
private final LiveData<List<Coin>> coins;
public CoinRepository(Application application)
coinDao = DB.getInstance(application).coinDao();
coins = coinDao.getAll();
loadCoinsPeriodically();
Log.d(TAG, "New instance created...");
public LiveData<List<Coin>> getCoins()
return coins;
private void loadCoinsPeriodically()
Observable.interval(0, 1, TimeUnit.MINUTES)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
aLong ->
RetrofitService.getClient().getLatest(500)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(
cryptoList ->
DB.databaseWriteExecutor.submit(() ->
coinDao.deleteAll();
coinDao.insertAll(cryptoList.getCoins());
Log.d(TAG, "Data inserted in \"coins\" table");
),
throwable ->
Log.d(TAG, "API observable error: " + throwable.getMessage())),
throwable ->
Log.d(TAG, "Periodic observable error: " + throwable.getMessage()));
public LiveData<Coin> getCoin(int id)
return coinDao.get(id);
我已经为 OP 的用例编写了一个示例应用程序,check this Github repo 以获取完整代码。
【讨论】:
Hiiiii ...非常感谢您提供此代码!我有一个错误,但我想我知道如何解决它。在这里你添加了`//根据你的逻辑修改这部分`我看到你正在使用Call<List<Coin>> call, Response<List<Coin>> response
所以我想......我需要将其更改为Call<CryptoList> call, Response<CryptoList> response
这是我用来调用API的...我会试试这个。
我根据您的代码更新问题...。再次,非常感谢您的帮助!!!!如果update 8-6-2021
问题中的所有内容都清楚,请告诉我
请再次阅读完整答案,我已经提到:“确保您的呼叫响应返回List<Coin>
,否则您必须自己从CryptoList
创建一个List<Coin>
。”跨度>
您需要将您的呼叫更新为:Call<List<Coin>> doGetUserListAll(YOUR_PARAMS_HERE)
只是想确保您确实写了<List<Coin>>
而不是<List<Coins>>
我以为您对带有 s lol 的 Coins 类名感到困惑...我会再试一次并更改您的每个<List<Coins>>
到<List<Coin>>
再次感谢您的帮助【参考方案2】:
OkHttp 客户端默认可以并行发出 64 个请求。您可以使用该值并增加maxRequests 数字。创建新的 OkHttp 调度程序:
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(200);
然后,初始化 OkHttp 客户端:
OkHttpClient client = new OkHttpClient.Builder()
...
.dispatcher(dispatcher)
.build();
最后,将这个 OkHttp 添加到 Retrofit 中:
Retrofit retrofit = new Retrofit.Builder()
...
.client(client)
.build();
【讨论】:
嗨,有人建议将数据放入数据库中,然后从那里获取数据..因为对 api 进行如此多的调用并不是一个好主意。你能提供代码吗?或教程或任何引导我到那里..以上是关于如何从 api 获取数据并将数据插入 Android Studio 中的 ROOM(数据库)?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Express API 获取数据并将其显示到 React 应用程序?