1

Я довольно новичок в разработке Android и в настоящее время пытаюсь написать приложение, которое покажет завтрашнюю погоду в нескольких городах. Извините за любые неиспользуемые терминалы, которые я мог бы использовать в этом вопросе.Android. Извлеките данные из базы данных и затем выполните сетевой запрос. Как реализовать?

То, что я хочу, чтобы достичь:

App будет получать данные из локальной базы данных, а затем построить HTTP запрос на данные, извлекаемых из БД, получить ответ JSON и сформировать список элементов.

То, что я в настоящее время:

Все, кроме функциональности SQL.

Это снимок моего основного кода активности. Я использую LoaderCallbacks<List<Weather>> для создания URI с необходимыми параметрами в onCreateLoader(int i, Bundle bundle), отправьте HTTP-запрос и получите данные через WeatherLoader(this, uriList), а результаты элементов формы - List, используя WeatherAdapter.

public class WeatherActivity extends AppCompatActivity 
implements LoaderCallbacks<List<Weather>>, 
SharedPreferences.OnSharedPreferenceChangeListener { 

private static final int WEATHER_LOADER_ID = 1; 
private WeatherAdapter mAdapter; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.weather_activity); 

    ListView weatherListView = (ListView) findViewById(R.id.list); 

    mEmptyStateTextView = (TextView) findViewById(R.id.empty_view); 
    weatherListView.setEmptyView(mEmptyStateTextView); 
    mAdapter = new WeatherAdapter(this, new ArrayList<Weather>()); 
    weatherListView.setAdapter(mAdapter); 

    ... 

    weatherListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 
     @Override 
     public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) { 
      Weather currentWeather = mAdapter.getItem(position); 
      Uri forecastUri = Uri.parse(currentWeather.getUrl()); 
      Intent websiteIntent = new Intent(Intent.ACTION_VIEW, forecastUri); 
      startActivity(websiteIntent); 
     } 
    }); 

    ConnectivityManager connMgr = (ConnectivityManager) 
      getSystemService(Context.CONNECTIVITY_SERVICE); 
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); 
    if (networkInfo != null && networkInfo.isConnected()) { 
     LoaderManager loaderManager = getLoaderManager(); 
     loaderManager.initLoader(WEATHER_LOADER_ID, null, this); 
    } else { 
     View loadingIndicator = findViewById(R.id.loading_indicator); 
     loadingIndicator.setVisibility(View.GONE); 
     mEmptyStateTextView.setText(R.string.no_internet_connection); 
    } 
} 

@Override 
public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) { 

    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); 
    String tempUnit = sharedPrefs.getString(
      getString(R.string.settings_temp_unit_key), 
      getString(R.string.settings_temp_unit_default)); 

    List<String> uriList = new ArrayList<>(); 

    /*** 
    * 
    * Here we input cities for which we want to see the forecast 
    * 
    * ***/ 

    List<String> cities = new ArrayList<>(); 
    cities.add("London,uk"); 
    cities.add("Kiev,ua"); 
    cities.add("Berlin,de"); 
    cities.add("Dubai,ae"); 

    //For each city in the list generate URI and put it in the URIs list 
    for (String city : cities){ 
     Uri baseUri = Uri.parse(OWM_REQUEST_URL); 
     Uri.Builder uriBuilder = baseUri.buildUpon(); 

     uriBuilder.appendQueryParameter("q", city); 
     uriBuilder.appendQueryParameter("cnt", "16"); 
     uriBuilder.appendQueryParameter("units", tempUnit); 
     uriBuilder.appendQueryParameter("appid", "some_key"); 

     uriList.add(uriBuilder.toString()); 
    } 

    return new WeatherLoader(this, uriList); 
} 

@Override 
public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) { 

    mAdapter.clear(); 

    // If there is a valid list of forecasts, then add them to the adapter's 
    // data set. This will trigger the ListView to update. 
    if (weatherList != null && !weatherList.isEmpty()) { 
     mAdapter.addAll(weatherList); 
    } 
} 

@Override 
public void onLoaderReset(Loader<List<Weather>> loader) { 
    mAdapter.clear(); 
} 

Как вы видите, города являются "жёстко" через List<String> cities = new ArrayList<>(); в onCreateLoader(int i, Bundle bundle). Вот почему я решил реализовать SQL-хранилище городов в своем приложении. Я знаю, как реализовать функциональность SQL в приложении Android, используя ContentProvider и CursorAdapter.

Так в чем проблема?

Если я прав, мы должны использовать LoaderManager.LoaderCallbacks<Cursor>, если мы хотим сделать запрос к локальной базе данных.

К сожалению, я не могу представить, как объединить текущие LoaderCallbacks<List<Weather>> и LoaderCallbacks<Cursor> в одном действии, чтобы заставить его работать так, как я хочу.

На самом деле, я хочу изменить List<String> cities = new ArrayList<>(); на что-то вроде Cursor cursor = new CursorLoader(this, WeatherEntry.CONTENT_URI, projection, null, null, null); построить URI на результаты, которые CursorLoader возвращается.

Но мы должны сделать SQL-запрос в отдельном потоке и HTTP-запросе также (!) В отдельном потоке. Должны ли мы делать вложенные потоки/загрузчики (HTTP-запрос в области sql-выборки данных и возвращать List<T>)? Даже не представляю, как это можно сделать, если так ...

Помогите мне, пожалуйста, я застрял!

ответ

1

Хорошо, это было не очевидно для меня с первого взгляда, но я, наконец, решил проблему.

В вопросе выше мы имели список городов, которые были жёстко:

List<String> cities = new ArrayList<>(); 
cities.add("London,uk"); 
cities.add("Kiev,ua"); 
cities.add("Berlin,de"); 
cities.add("Dubai,ae"); 

Даже если мы предположим, что мы изменим его на запрос БД, как это:

// Connect to a DB 
... 
Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection, 
        null, null, null, 
        null, null, null); 
... 
// Fetch data from cursor 

. ... мы будем иметь этот SQL-запрос в основном потоке. Поэтому нам нужно решение.

Самое лучшее, что я нашел для меня, это положить, что SQL запрос в CustomLoader класса и передать необходимые параметры в конструкторе (в моем случае это параметр SharedPreferences к построенному запроса HTTP).

Вот мой код:

WeatherActivity.java

public class WeatherActivity extends AppCompatActivity implements LoaderCallbacks<List<Weather>>, 
     SharedPreferences.OnSharedPreferenceChangeListener { 
    ... 
    @Override 
    public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) { 

     SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); 
     String tempUnit = sharedPrefs.getString(
       getString(R.string.settings_temp_unit_key), 
       getString(R.string.settings_temp_unit_default)); 

     return new WeatherLoader(this, tempUnit); 
    } 

    @Override 
    public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) { 
     // Hide loading indicator because the data has been loaded 
     View loadingIndicator = findViewById(R.id.loading_indicator); 
     loadingIndicator.setVisibility(View.GONE); 

     // Set empty state text to display "No forecasts found." 
     mEmptyStateTextView.setText(R.string.no_forecasts); 

     // Clear the adapter of previous forecasts data 
     mAdapter.clear(); 

     // If there is a valid list of forecasts, then add them to the adapter's 
     // data set. This will trigger the ListView to update. 
     if (weatherList != null && !weatherList.isEmpty()) { 
      mAdapter.addAll(weatherList); 
     } 
    } 

WeatherLoader.java

public class WeatherLoader extends AsyncTaskLoader<List<Weather>> { 
... 
    // Pass parameters here from WeatherActivity 
    public WeatherLoader(Context context, String tmpUnit) { 
     super(context); 
     mTempUnit = tmpUnit; 
    } 

    @Override 
    protected void onStartLoading() { 
     forceLoad(); 
    } 

    /** 
    * This is on a background thread. 
    */ 
    @Override 
    public List<Weather> loadInBackground() { 

     // List for storing built URIs 
     List<String> uriList = new ArrayList<>(); 
     // List for storing forecast cities 
     List<String> cities = new ArrayList<>(); 

     // Define a projection that specifies the columns from the table we care about. 
     ... 

     Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection, 
       null, null, null, 
       null, null, null); 

     // Get list of cities from cursor 
     ... 

     //For each city in the list generate URI and put it in the URIs list 
     for (String city : cities){ 
      Uri baseUri = Uri.parse(OWM_REQUEST_URL); 
      Uri.Builder uriBuilder = baseUri.buildUpon(); 

      uriBuilder.appendQueryParameter("q", city); 
      uriBuilder.appendQueryParameter("cnt", "16"); 
      uriBuilder.appendQueryParameter("units", mTempUnit); 
      uriBuilder.appendQueryParameter("appid", /*some id*/); 

      uriList.add(uriBuilder.toString()); 
     } 

     if (uriList == null) { 
      return null; 
     } 

     // Perform the network request, parse the response, and extract a list of forecasts. 
     List<Weather> forecasts = QueryUtils.fetchForecastData(uriList); 
     return forecasts; 
    } 

Так что у нас есть?

Мы реализовали постоянное хранилище данных в рамках работы с ArrayAdapter, которые затем используются для HTTP-запроса. SQL-запрос находится в отдельном потоке, и у нас не будет проблем с производительностью приложения.

Надеюсь, что это поможет кому-то, у нас хороший день!