如何从 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&lt;Coin&gt;,否则您必须自己从 CryptoList 创建一个 List&lt;Coin&gt;

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&lt;List&lt;Coin&gt;&gt; call, Response&lt;List&lt;Coin&gt;&gt; response所以我想......我需要将其更改为Call&lt;CryptoList&gt; call, Response&lt;CryptoList&gt; response这是我用来调用API的...我会试试这个。 我根据您的代码更新问题...。再次,非常感谢您的帮助!!!!如果update 8-6-2021 问题中的所有内容都清楚,请告诉我 请再次阅读完整答案,我已经提到:“确保您的呼叫响应返回List&lt;Coin&gt;,否则您必须自己从CryptoList 创建一个List&lt;Coin&gt;。”跨度> 您需要将您的呼叫更新为:Call&lt;List&lt;Coin&gt;&gt; doGetUserListAll(YOUR_PARAMS_HERE) 只是想确保您确实写了&lt;List&lt;Coin&gt;&gt; 而不是&lt;List&lt;Coins&gt;&gt; 我以为您对带有 s lol 的 Coins 类名感到困惑...我会再试一次并更改您的每个&lt;List&lt;Coins&gt;&gt;&lt;List&lt;Coin&gt;&gt; 再次感谢您的帮助【参考方案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(数据库)?的主要内容,如果未能解决你的问题,请参考以下文章

如何从表中获取数据并将其插入另一个表?

如何从 Web Api 获取请求和响应并将其保存到数据库

如何从今天的日期获取下个月的日期并将其插入我的数据库中?

如何从 Express API 获取数据并将其显示到 React 应用程序?

如何从 api 获取 json 数据并将其以嵌入形式发送到 discord.py 中?

从 PowerShell 将 NULL 插入数据库