RecyclerView 即使在收到数据后也没有显示任何内容
RecyclerView not showing anything even after receiving data
我正在尝试在 RecyclerView
上填充一些数据。通过其中一种方式获取数据:
- 如果互联网存在(使用
Retrofit
和RxJava2
),从API请求数据,将数据保存到本地数据库(使用Room
),发送数据到 View
(在我的例子中是 Fragment
)。
- 如果没有互联网则从本地数据库获取数据并将其发送到
View
。
我使用 MVP 作为应用程序架构,我使用 ViewModels
来处理 UI 的数据处理。我正在使用 ButterKnife
进行视图查找。
Presenter
调用Repository
,根据上面指定的条件获取数据,然后Presenter
将数据发送到Views
where ViewModel
拦截数据并执行必要的步骤将数据传递给 UI 元素(RecyclerView
适配器更新)。
我的问题是:
- 即使我在调试器中看到数据到达
RecyclerViewAdapter
,屏幕也是空白的。没有显示数据。
- 本地数据库的结果抛出
IllegalStateException
,表示无法从主线程访问本地数据库,即使我subscribeOn(Schedulers.io())
调用函数return 来自本地数据库的数据,其中 return 一个 Maybe<>
。
我的代码如下:
存储库:
public class CountriesListApiRepository {
private final IRestServiceDataFetcher restServiceDataFetcher; // Retrofit interface to get data from API
@Inject
public CountriesListApiRepository(IRestServiceDataFetcher restServiceDataFetcher) {
this.restServiceDataFetcher = restServiceDataFetcher;
}
public Maybe<List<CountriesFullEntity>> getCountriesFromApi() {
return restServiceDataFetcher
.getListOfCountriesData()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
}
}
public class CountriesListLocalDbRepository {
private final CountriesLocalDb countriesLocalDb;
@Inject
CountriesListLocalDbRepository(CountriesLocalDb countriesLocalDb) {
this.countriesLocalDb = countriesLocalDb;
}
Maybe<List<CountriesFullEntity>> getCountriesFromLocalDb() {
return this.countriesLocalDb
.getCountriesLocalDbDAO()
.getCountriesList()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io());
}
void updateLocalDb(List<CountriesFullEntity> updatedCountriesList) {
countriesLocalDb.getCountriesLocalDbDAO().insertAllCountries(updatedCountriesList);
}
}
public class CountriesListRepository {
private final CountriesListApiRepository countriesListApiRepository;
private final CountriesListLocalDbRepository countriesListLocalDbRepository;
private static final String TAG_CountriesListRepository = "CountriesListRepository";
@Inject
public CountriesListRepository(CountriesListApiRepository countriesListApiRepository,
CountriesListLocalDbRepository countriesListLocalDbRepository) {
this.countriesListApiRepository = countriesListApiRepository;
this.countriesListLocalDbRepository = countriesListLocalDbRepository;
}
public Maybe<List<CountriesFullEntity>> getCountries() {
return this.countriesListApiRepository
.getCountriesFromApi()
.doOnSuccess(this.countriesListLocalDbRepository::updateLocalDb)
.doOnError(throwable -> {
Log.d(TAG_CountriesListRepository,throwable.getMessage());
throwable.printStackTrace(); })
.onErrorResumeNext(this.countriesListLocalDbRepository.getCountriesFromLocalDb());
}
}
我的主持人:
public class BasePresenter<T extends BaseView> {
@Inject
T injectedView;
private static final String TAG_BASE_PRESENTER = "BasePresenter";
T getInjectedView() {
return injectedView;
}
<V> void subscribeToObserver(Maybe<V> flowable, MaybeObserver<V> maybeObserver) {
flowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> Log.d(TAG_BASE_PRESENTER,"Subscribed"))
.doOnSuccess(v -> Log.d(TAG_BASE_PRESENTER,"onNext() completed"))
.subscribe(maybeObserver);
}
}
public class CountriesListPresenter extends BasePresenter<CountriesListView>
implements MaybeObserver<List<CountriesFullEntity>> {
private static final String TAG_ListPresenter = "CountriesListPresenter";
private final CountriesListRepository countriesListRepository;
private Disposable disposable;
@Inject
public CountriesListPresenter(CountriesListRepository countriesListRepository) {
this.countriesListRepository = countriesListRepository;
}
public void getCountries() {
Maybe<List<CountriesFullEntity>> listOfCountriesData = this.countriesListRepository
.getCountries();
subscribeToObserver(listOfCountriesData, this);
}
public void updateCountriesList(@NonNull Boolean isInternetThere) {
Maybe<List<CountriesFullEntity>> newCountriesFullData = this.countriesListRepository
.updateCountriesList(isInternetThere);
subscribeToObserver(newCountriesFullData,this);
}
@Override
public void onSubscribe(Disposable d) {
this.disposable = d;
}
@Override
public void onSuccess(List<CountriesFullEntity> countriesFullEntities) {
Log.d(TAG_ListPresenter,"Executing onNext()");
getInjectedView().onLoadCountriesDataFull(countriesFullEntities);
}
@Override
public void onError(Throwable e) {
Log.d(TAG_ListPresenter,"Executing onError()",e);
getInjectedView().onErrorEncountered(e.getMessage());
}
@Override
public void onComplete() {
if(!disposable.isDisposed()) disposable.dispose();
}
}
我的观点:
public class MainActivity extends AppCompatActivity implements IFragmentToFragmentMediator, SwipeRefreshLayout.OnRefreshListener {
public static final String TAG_MAIN_ACTIVITY = "MainActivity";
public static final String TAG_LIST_FRAGMENT = "COUNTRY_LIST";
public static final String TAG_DETAILS_FRAGMENT = "COUNTRY_DETAILS";
@BindView(R.id.MainActSwipeRefreshLayout)
SwipeRefreshLayout swipeRefreshLayout;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
swipeRefreshLayout.setOnRefreshListener(this);
Configuration config = getResources().getConfiguration();
Log.d(TAG_MAIN_ACTIVITY,"Main Activity View Created");
if(config.smallestScreenWidthDp<600){
CountriesListFrag countriesListFrag = new CountriesListFrag();
conductFragmentTransaction(countriesListFrag,TAG_LIST_FRAGMENT, false, true);
Log.d(TAG_MAIN_ACTIVITY,"Main Activity < 600 and portrait");
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager fm = getSupportFragmentManager();
if(getSupportFragmentManager().getBackStackEntryCount() > 1) {
Fragment f = fm.findFragmentByTag(TAG_DETAILS_FRAGMENT);
if (f instanceof CountryDetailsFrag) fm.popBackStack();
else this.finish();
}
}
@Override
public void onRefresh() {
// TODO : Code the refreshing data callback here
Fragment countryListFragment = returnNonNullRunningFragmentByTagName(TAG_LIST_FRAGMENT);
Fragment countryDetailsFragment = returnNonNullRunningFragmentByTagName(TAG_DETAILS_FRAGMENT);
makeViewsSignalUpdateOfData((CountriesListFrag)countryListFragment);
makeViewsSignalUpdateOfData((CountryDetailsFrag)countryDetailsFragment);
}
@Override
public void invokeDetailsFragmentOnListItemCLickedInListFragment(CountriesFullEntity countriesFullEntity) {
CountryDetailsFrag countryDetailsFrag = CountryDetailsFrag.newInstance();
Bundle data = new Bundle();
data.putSerializable("data",countriesFullEntity);
countryDetailsFrag.setArguments(data);
conductFragmentTransaction(countryDetailsFrag, TAG_DETAILS_FRAGMENT, false, true);
}
@Override
public void invokeDetailsFragmentOnListItemClickedInListFragmentViewModel() {
CountryDetailsFrag countryDetailsFrag =
(CountryDetailsFrag) getSupportFragmentManager().findFragmentByTag(TAG_DETAILS_FRAGMENT);
if(countryDetailsFrag!=null && countryDetailsFrag.isVisible())
conductFragmentTransaction(countryDetailsFrag, TAG_DETAILS_FRAGMENT, true, true);
}
private void makeViewsSignalUpdateOfData(BaseView view) {
if(view != null) view.onPerformUpdateAction();
}
private Fragment returnNonNullRunningFragmentByTagName(String tagName) {
Fragment fragmentForProcessing = getSupportFragmentManager().findFragmentByTag(tagName);
if(fragmentForProcessing!=null && fragmentForProcessing.isVisible()) return fragmentForProcessing;
return null;
}
private void conductFragmentTransaction(Fragment targetFragment, String tag, boolean addOrReplaceFlag, boolean backStackFlag) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if(addOrReplaceFlag)fragmentTransaction.replace(R.id.fragmentCanvas,targetFragment,tag);
else fragmentTransaction.add(R.id.fragmentCanvas,targetFragment,tag);
if(backStackFlag)fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
public class CountriesListFrag extends Fragment implements CountriesListView {
public static final String TAG_LIST_FRAGMENT = "ListFragment";
@Inject
Picasso picasso;
@Inject
CountriesListPresenter countriesListPresenter;
private IFragmentToFragmentMediator listeningActivity;
private Frag2FragCommViewModel frag2FragCommViewModel;
@BindView(R.id.CountriesEntireHolderRV)
RecyclerView countriesEntireHolderRV;
private CountriesListRecyclerViewAdapter countriesListRecyclerViewAdapter;
private Unbinder unbinder;
public CountriesListFrag() {
}
@Override
public void onAttach(Context context) {
DaggerCountryComponents
.builder()
.appComponents(((CentralApplication)context.getApplicationContext())
.getAppComponents())
.countryModule(new CountryModule(this))
.build()
.inject(this);
super.onAttach(context);
try{
this.listeningActivity = (MainActivity) context;
this.frag2FragCommViewModel = ViewModelProviders
.of((MainActivity) context)
.get(Frag2FragCommViewModel.class);
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement IFragmentToFragmentMediator");
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_countries_list, container, false);
if(v!=null) this.unbinder = ButterKnife.bind(this,v);
// Inflate the layout for this fragment
initialize();
this.countriesListPresenter.getCountries();
setUpListItemClickListener();
frag2FragCommViewModel
.getLiveDataListOfCountriesData()
.observe(this,countriesFullEntities ->
countriesListRecyclerViewAdapter.setCountriesFullEntityList(countriesFullEntities));
Log.d(TAG_LIST_FRAGMENT,"List Fragment View Created");
return v;
}
@Override
public void onLoadCountriesDataFull(List<CountriesFullEntity> countriesFullData) {
// this.countriesListRecyclerViewAdapter.setCountriesFullEntityList(countriesFullData);
Log.d(TAG_LIST_FRAGMENT,"Data Received in List Fragment");
Log.d(TAG_LIST_FRAGMENT,countriesFullData.get(0).getName());
this.frag2FragCommViewModel.setListOfCountriesData(countriesFullData);
}
@Override
public void onErrorEncountered(String errorMessage) {
Toast.makeText(getContext(), errorMessage,Toast.LENGTH_SHORT).show();
}
@Override
public void onPerformUpdateAction() {
callToPresenterToUpdateListOfCountriesOnInternetPresent(this.getContext());
}
private void initialize() {
this.countriesEntireHolderRV = new RecyclerView(this.getContext());
this.countriesListRecyclerViewAdapter = new CountriesListRecyclerViewAdapter(new ArrayList<>(),
LayoutInflater.from(this.getContext()), this.picasso);
this.countriesEntireHolderRV.setLayoutManager(new LinearLayoutManager(this.getContext()));
this.countriesEntireHolderRV
.addItemDecoration(new DividerItemDecoration(this.countriesEntireHolderRV.getContext()
,DividerItemDecoration.VERTICAL));
this.countriesEntireHolderRV.setAdapter(this.countriesListRecyclerViewAdapter);
Log.d(TAG_LIST_FRAGMENT,"List Fragment init");
}
@Override
public void onDestroyView() {
super.onDestroyView();
this.unbinder.unbind();
}
}
public class CountriesListRecyclerViewAdapter extends RecyclerView.Adapter<CountriesListRecyclerViewAdapter.CountriesListViewHolder>{
private List<CountriesFullEntity> countriesFullEntityList;
private final LayoutInflater layoutInflater;
private final Picasso picasso;
private CountriesListRVClickListener countriesListRVClickListener;
private int currentPosition;
CountriesListRecyclerViewAdapter(List<CountriesFullEntity> countriesFullEntityList, LayoutInflater layoutInflater, Picasso picasso) {
this.countriesFullEntityList = countriesFullEntityList;
this.layoutInflater = layoutInflater;
this.picasso = picasso;
this.currentPosition = 0;
}
@NonNull
@Override
public CountriesListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new CountriesListViewHolder(this.layoutInflater.inflate(R.layout.countries_list_layout,parent,false));
}
@Override
public void onBindViewHolder(@NonNull CountriesListViewHolder holder, int position) {
CountriesFullEntity countriesFullEntity = countriesFullEntityList.get(position);
picasso.load(countriesFullEntity.getFlag()).into(holder.countryIcon);
holder.nameOfCountry.setText(countriesFullEntity.getName());
}
@Override
public int getItemCount() {
return this.countriesFullEntityList.size();
}
void setItemClickListener(CountriesListRVClickListener itemClickListener) {
this.countriesListRVClickListener = itemClickListener;
}
CountriesFullEntity getCountriesFullEntityAtPosition(int position) {
return this.countriesFullEntityList.get(position);
}
void setCountriesFullEntityList(List<CountriesFullEntity> countriesFullEntityList) {
this.countriesFullEntityList = countriesFullEntityList;
notifyDataSetChanged();
}
int getCurrentPosition() {
return currentPosition;
}
void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
class CountriesListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@BindView(R.id.NameOfCountry)
TextView nameOfCountry;
@BindView(R.id.CountryIcon)
ImageView countryIcon;
CountriesListViewHolder(View itemView) {
super(itemView);
}
@Override
public void onClick(View v) {
countriesListRVClickListener.onListItemClicked(v,getAdapterPosition());
}
}
}
我的 DAO:
@Dao
public interface CountriesLocalDbDAO {
@Query("SELECT * FROM Countries")
Maybe<List<CountriesFullEntity>> getCountriesList();
@Query("SELECT * FROM Countries WHERE name LIKE :countryName")
Maybe<CountriesFullEntity> getCountryByName(String countryName);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAllCountries(List<CountriesFullEntity> countryList);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertSingleCountry(CountriesFullEntity singleCountry);
@Delete
void deleteSingleCountry(CountriesFullEntity singleCountry);
@Delete
void endOfTheWorld(List<CountriesFullEntity> theWorld);
}
public interface IRestServiceDataFetcher {
@GET("all")
Maybe<List<CountriesFullEntity>> getListOfCountriesData();
@GET("name/{name}")
Maybe<CountriesFullEntity> getParticularCountry(@Path("name") String name);
}
我的布局:
ActivityMainLayout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/fragmentCanvas"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/MainActSwipeRefreshLayout"
android:layout_height="20dp"
android:layout_width="match_parent">
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
片段列表布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.countrieslist.CountriesListFrag">
<!-- TODO: Update blank fragment layout -->
<android.support.v7.widget.RecyclerView
android:id="@+id/CountriesEntireHolderRV"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
RecyclerViewViewHolderLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/CountryIcon"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/app_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/NameOfCountry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/app_name"
android:textSize="40sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/CountryIcon"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
我的应用程序 build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "shankhadeepghoshal.org.countrieslistapp"
minSdkVersion 14
//noinspection OldTargetApi
targetSdkVersion 27
multiDexEnabled true
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
externalNativeBuild {
cmake {
cppFlags "-std=c++14 -frtti -fexceptions"
}
}
// multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion '27.0.3'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
implementation 'com.google.dagger:dagger-android:2.16'
implementation 'com.google.dagger:dagger-android-support:2.16'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation(
[group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.6'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.6'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.6']
)
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-java8:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
compileOnly 'org.projectlombok:lombok:1.18.2'
annotationProcessor 'org.projectlombok:lombok:1.18.2'
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
implementation "android.arch.persistence.room:rxjava2:$room_version"
implementation "android.arch.persistence.room:guava:$room_version"
testImplementation "android.arch.persistence.room:testing:$room_version"
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "android.arch.lifecycle:runtime:$lifecycle_version"
implementation "android.arch.lifecycle:common-java8:$lifecycle_version"
implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"
testImplementation "android.arch.core:core-testing:$lifecycle_version"
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:support-compat:27.1.1'
implementation 'com.android.support:exifinterface:27.1.1'
implementation 'com.android.support:multidex:1.0.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
更新:
我做了@Ben-P 在他的回答中建议的。但是现在我的 RecycleViewAdapter 给我错误。
我在这条线上得到一个 IllegalArgumentException - Target must be null
: picasso.load(countriesFullEntity.getFlag()).into(holder.countryIcon);
in onBindViewHolder(holder,position)
在您的片段中,您有以下代码:
private void initialize() {
this.countriesEntireHolderRV = new RecyclerView(this.getContext());
...
}
早些时候,在 onCreateView()
中,您调用 ButterKnife.bind()
... 这意味着上面的代码正在将 countriesEntireHolderRV
重新分配给新的引用。这个新的 RecyclerView 永远不会添加到您的 Fragment 视图中(并且可能无论如何都不可见,因为 LinearLayout 已经有一个 child 占据了所有可见的 space),所以您的所有操作都与用户看不到的 RecyclerView。
只需从 initialize()
中删除该行即可。然后你将在你的布局文件中使用 RecyclerView,一切都会出现。
我正在尝试在 RecyclerView
上填充一些数据。通过其中一种方式获取数据:
- 如果互联网存在(使用
Retrofit
和RxJava2
),从API请求数据,将数据保存到本地数据库(使用Room
),发送数据到View
(在我的例子中是Fragment
)。 - 如果没有互联网则从本地数据库获取数据并将其发送到
View
。
我使用 MVP 作为应用程序架构,我使用 ViewModels
来处理 UI 的数据处理。我正在使用 ButterKnife
进行视图查找。
Presenter
调用Repository
,根据上面指定的条件获取数据,然后Presenter
将数据发送到Views
where ViewModel
拦截数据并执行必要的步骤将数据传递给 UI 元素(RecyclerView
适配器更新)。
我的问题是:
- 即使我在调试器中看到数据到达
RecyclerViewAdapter
,屏幕也是空白的。没有显示数据。 - 本地数据库的结果抛出
IllegalStateException
,表示无法从主线程访问本地数据库,即使我subscribeOn(Schedulers.io())
调用函数return 来自本地数据库的数据,其中 return 一个Maybe<>
。
我的代码如下:
存储库:
public class CountriesListApiRepository {
private final IRestServiceDataFetcher restServiceDataFetcher; // Retrofit interface to get data from API
@Inject
public CountriesListApiRepository(IRestServiceDataFetcher restServiceDataFetcher) {
this.restServiceDataFetcher = restServiceDataFetcher;
}
public Maybe<List<CountriesFullEntity>> getCountriesFromApi() {
return restServiceDataFetcher
.getListOfCountriesData()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
}
}
public class CountriesListLocalDbRepository {
private final CountriesLocalDb countriesLocalDb;
@Inject
CountriesListLocalDbRepository(CountriesLocalDb countriesLocalDb) {
this.countriesLocalDb = countriesLocalDb;
}
Maybe<List<CountriesFullEntity>> getCountriesFromLocalDb() {
return this.countriesLocalDb
.getCountriesLocalDbDAO()
.getCountriesList()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io());
}
void updateLocalDb(List<CountriesFullEntity> updatedCountriesList) {
countriesLocalDb.getCountriesLocalDbDAO().insertAllCountries(updatedCountriesList);
}
}
public class CountriesListRepository {
private final CountriesListApiRepository countriesListApiRepository;
private final CountriesListLocalDbRepository countriesListLocalDbRepository;
private static final String TAG_CountriesListRepository = "CountriesListRepository";
@Inject
public CountriesListRepository(CountriesListApiRepository countriesListApiRepository,
CountriesListLocalDbRepository countriesListLocalDbRepository) {
this.countriesListApiRepository = countriesListApiRepository;
this.countriesListLocalDbRepository = countriesListLocalDbRepository;
}
public Maybe<List<CountriesFullEntity>> getCountries() {
return this.countriesListApiRepository
.getCountriesFromApi()
.doOnSuccess(this.countriesListLocalDbRepository::updateLocalDb)
.doOnError(throwable -> {
Log.d(TAG_CountriesListRepository,throwable.getMessage());
throwable.printStackTrace(); })
.onErrorResumeNext(this.countriesListLocalDbRepository.getCountriesFromLocalDb());
}
}
我的主持人:
public class BasePresenter<T extends BaseView> {
@Inject
T injectedView;
private static final String TAG_BASE_PRESENTER = "BasePresenter";
T getInjectedView() {
return injectedView;
}
<V> void subscribeToObserver(Maybe<V> flowable, MaybeObserver<V> maybeObserver) {
flowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> Log.d(TAG_BASE_PRESENTER,"Subscribed"))
.doOnSuccess(v -> Log.d(TAG_BASE_PRESENTER,"onNext() completed"))
.subscribe(maybeObserver);
}
}
public class CountriesListPresenter extends BasePresenter<CountriesListView>
implements MaybeObserver<List<CountriesFullEntity>> {
private static final String TAG_ListPresenter = "CountriesListPresenter";
private final CountriesListRepository countriesListRepository;
private Disposable disposable;
@Inject
public CountriesListPresenter(CountriesListRepository countriesListRepository) {
this.countriesListRepository = countriesListRepository;
}
public void getCountries() {
Maybe<List<CountriesFullEntity>> listOfCountriesData = this.countriesListRepository
.getCountries();
subscribeToObserver(listOfCountriesData, this);
}
public void updateCountriesList(@NonNull Boolean isInternetThere) {
Maybe<List<CountriesFullEntity>> newCountriesFullData = this.countriesListRepository
.updateCountriesList(isInternetThere);
subscribeToObserver(newCountriesFullData,this);
}
@Override
public void onSubscribe(Disposable d) {
this.disposable = d;
}
@Override
public void onSuccess(List<CountriesFullEntity> countriesFullEntities) {
Log.d(TAG_ListPresenter,"Executing onNext()");
getInjectedView().onLoadCountriesDataFull(countriesFullEntities);
}
@Override
public void onError(Throwable e) {
Log.d(TAG_ListPresenter,"Executing onError()",e);
getInjectedView().onErrorEncountered(e.getMessage());
}
@Override
public void onComplete() {
if(!disposable.isDisposed()) disposable.dispose();
}
}
我的观点:
public class MainActivity extends AppCompatActivity implements IFragmentToFragmentMediator, SwipeRefreshLayout.OnRefreshListener {
public static final String TAG_MAIN_ACTIVITY = "MainActivity";
public static final String TAG_LIST_FRAGMENT = "COUNTRY_LIST";
public static final String TAG_DETAILS_FRAGMENT = "COUNTRY_DETAILS";
@BindView(R.id.MainActSwipeRefreshLayout)
SwipeRefreshLayout swipeRefreshLayout;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
swipeRefreshLayout.setOnRefreshListener(this);
Configuration config = getResources().getConfiguration();
Log.d(TAG_MAIN_ACTIVITY,"Main Activity View Created");
if(config.smallestScreenWidthDp<600){
CountriesListFrag countriesListFrag = new CountriesListFrag();
conductFragmentTransaction(countriesListFrag,TAG_LIST_FRAGMENT, false, true);
Log.d(TAG_MAIN_ACTIVITY,"Main Activity < 600 and portrait");
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager fm = getSupportFragmentManager();
if(getSupportFragmentManager().getBackStackEntryCount() > 1) {
Fragment f = fm.findFragmentByTag(TAG_DETAILS_FRAGMENT);
if (f instanceof CountryDetailsFrag) fm.popBackStack();
else this.finish();
}
}
@Override
public void onRefresh() {
// TODO : Code the refreshing data callback here
Fragment countryListFragment = returnNonNullRunningFragmentByTagName(TAG_LIST_FRAGMENT);
Fragment countryDetailsFragment = returnNonNullRunningFragmentByTagName(TAG_DETAILS_FRAGMENT);
makeViewsSignalUpdateOfData((CountriesListFrag)countryListFragment);
makeViewsSignalUpdateOfData((CountryDetailsFrag)countryDetailsFragment);
}
@Override
public void invokeDetailsFragmentOnListItemCLickedInListFragment(CountriesFullEntity countriesFullEntity) {
CountryDetailsFrag countryDetailsFrag = CountryDetailsFrag.newInstance();
Bundle data = new Bundle();
data.putSerializable("data",countriesFullEntity);
countryDetailsFrag.setArguments(data);
conductFragmentTransaction(countryDetailsFrag, TAG_DETAILS_FRAGMENT, false, true);
}
@Override
public void invokeDetailsFragmentOnListItemClickedInListFragmentViewModel() {
CountryDetailsFrag countryDetailsFrag =
(CountryDetailsFrag) getSupportFragmentManager().findFragmentByTag(TAG_DETAILS_FRAGMENT);
if(countryDetailsFrag!=null && countryDetailsFrag.isVisible())
conductFragmentTransaction(countryDetailsFrag, TAG_DETAILS_FRAGMENT, true, true);
}
private void makeViewsSignalUpdateOfData(BaseView view) {
if(view != null) view.onPerformUpdateAction();
}
private Fragment returnNonNullRunningFragmentByTagName(String tagName) {
Fragment fragmentForProcessing = getSupportFragmentManager().findFragmentByTag(tagName);
if(fragmentForProcessing!=null && fragmentForProcessing.isVisible()) return fragmentForProcessing;
return null;
}
private void conductFragmentTransaction(Fragment targetFragment, String tag, boolean addOrReplaceFlag, boolean backStackFlag) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if(addOrReplaceFlag)fragmentTransaction.replace(R.id.fragmentCanvas,targetFragment,tag);
else fragmentTransaction.add(R.id.fragmentCanvas,targetFragment,tag);
if(backStackFlag)fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
public class CountriesListFrag extends Fragment implements CountriesListView {
public static final String TAG_LIST_FRAGMENT = "ListFragment";
@Inject
Picasso picasso;
@Inject
CountriesListPresenter countriesListPresenter;
private IFragmentToFragmentMediator listeningActivity;
private Frag2FragCommViewModel frag2FragCommViewModel;
@BindView(R.id.CountriesEntireHolderRV)
RecyclerView countriesEntireHolderRV;
private CountriesListRecyclerViewAdapter countriesListRecyclerViewAdapter;
private Unbinder unbinder;
public CountriesListFrag() {
}
@Override
public void onAttach(Context context) {
DaggerCountryComponents
.builder()
.appComponents(((CentralApplication)context.getApplicationContext())
.getAppComponents())
.countryModule(new CountryModule(this))
.build()
.inject(this);
super.onAttach(context);
try{
this.listeningActivity = (MainActivity) context;
this.frag2FragCommViewModel = ViewModelProviders
.of((MainActivity) context)
.get(Frag2FragCommViewModel.class);
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement IFragmentToFragmentMediator");
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_countries_list, container, false);
if(v!=null) this.unbinder = ButterKnife.bind(this,v);
// Inflate the layout for this fragment
initialize();
this.countriesListPresenter.getCountries();
setUpListItemClickListener();
frag2FragCommViewModel
.getLiveDataListOfCountriesData()
.observe(this,countriesFullEntities ->
countriesListRecyclerViewAdapter.setCountriesFullEntityList(countriesFullEntities));
Log.d(TAG_LIST_FRAGMENT,"List Fragment View Created");
return v;
}
@Override
public void onLoadCountriesDataFull(List<CountriesFullEntity> countriesFullData) {
// this.countriesListRecyclerViewAdapter.setCountriesFullEntityList(countriesFullData);
Log.d(TAG_LIST_FRAGMENT,"Data Received in List Fragment");
Log.d(TAG_LIST_FRAGMENT,countriesFullData.get(0).getName());
this.frag2FragCommViewModel.setListOfCountriesData(countriesFullData);
}
@Override
public void onErrorEncountered(String errorMessage) {
Toast.makeText(getContext(), errorMessage,Toast.LENGTH_SHORT).show();
}
@Override
public void onPerformUpdateAction() {
callToPresenterToUpdateListOfCountriesOnInternetPresent(this.getContext());
}
private void initialize() {
this.countriesEntireHolderRV = new RecyclerView(this.getContext());
this.countriesListRecyclerViewAdapter = new CountriesListRecyclerViewAdapter(new ArrayList<>(),
LayoutInflater.from(this.getContext()), this.picasso);
this.countriesEntireHolderRV.setLayoutManager(new LinearLayoutManager(this.getContext()));
this.countriesEntireHolderRV
.addItemDecoration(new DividerItemDecoration(this.countriesEntireHolderRV.getContext()
,DividerItemDecoration.VERTICAL));
this.countriesEntireHolderRV.setAdapter(this.countriesListRecyclerViewAdapter);
Log.d(TAG_LIST_FRAGMENT,"List Fragment init");
}
@Override
public void onDestroyView() {
super.onDestroyView();
this.unbinder.unbind();
}
}
public class CountriesListRecyclerViewAdapter extends RecyclerView.Adapter<CountriesListRecyclerViewAdapter.CountriesListViewHolder>{
private List<CountriesFullEntity> countriesFullEntityList;
private final LayoutInflater layoutInflater;
private final Picasso picasso;
private CountriesListRVClickListener countriesListRVClickListener;
private int currentPosition;
CountriesListRecyclerViewAdapter(List<CountriesFullEntity> countriesFullEntityList, LayoutInflater layoutInflater, Picasso picasso) {
this.countriesFullEntityList = countriesFullEntityList;
this.layoutInflater = layoutInflater;
this.picasso = picasso;
this.currentPosition = 0;
}
@NonNull
@Override
public CountriesListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new CountriesListViewHolder(this.layoutInflater.inflate(R.layout.countries_list_layout,parent,false));
}
@Override
public void onBindViewHolder(@NonNull CountriesListViewHolder holder, int position) {
CountriesFullEntity countriesFullEntity = countriesFullEntityList.get(position);
picasso.load(countriesFullEntity.getFlag()).into(holder.countryIcon);
holder.nameOfCountry.setText(countriesFullEntity.getName());
}
@Override
public int getItemCount() {
return this.countriesFullEntityList.size();
}
void setItemClickListener(CountriesListRVClickListener itemClickListener) {
this.countriesListRVClickListener = itemClickListener;
}
CountriesFullEntity getCountriesFullEntityAtPosition(int position) {
return this.countriesFullEntityList.get(position);
}
void setCountriesFullEntityList(List<CountriesFullEntity> countriesFullEntityList) {
this.countriesFullEntityList = countriesFullEntityList;
notifyDataSetChanged();
}
int getCurrentPosition() {
return currentPosition;
}
void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
class CountriesListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@BindView(R.id.NameOfCountry)
TextView nameOfCountry;
@BindView(R.id.CountryIcon)
ImageView countryIcon;
CountriesListViewHolder(View itemView) {
super(itemView);
}
@Override
public void onClick(View v) {
countriesListRVClickListener.onListItemClicked(v,getAdapterPosition());
}
}
}
我的 DAO:
@Dao
public interface CountriesLocalDbDAO {
@Query("SELECT * FROM Countries")
Maybe<List<CountriesFullEntity>> getCountriesList();
@Query("SELECT * FROM Countries WHERE name LIKE :countryName")
Maybe<CountriesFullEntity> getCountryByName(String countryName);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAllCountries(List<CountriesFullEntity> countryList);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertSingleCountry(CountriesFullEntity singleCountry);
@Delete
void deleteSingleCountry(CountriesFullEntity singleCountry);
@Delete
void endOfTheWorld(List<CountriesFullEntity> theWorld);
}
public interface IRestServiceDataFetcher {
@GET("all")
Maybe<List<CountriesFullEntity>> getListOfCountriesData();
@GET("name/{name}")
Maybe<CountriesFullEntity> getParticularCountry(@Path("name") String name);
}
我的布局:
ActivityMainLayout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/fragmentCanvas"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/MainActSwipeRefreshLayout"
android:layout_height="20dp"
android:layout_width="match_parent">
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
片段列表布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.countrieslist.CountriesListFrag">
<!-- TODO: Update blank fragment layout -->
<android.support.v7.widget.RecyclerView
android:id="@+id/CountriesEntireHolderRV"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
RecyclerViewViewHolderLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/CountryIcon"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/app_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/NameOfCountry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/app_name"
android:textSize="40sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/CountryIcon"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
我的应用程序 build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "shankhadeepghoshal.org.countrieslistapp"
minSdkVersion 14
//noinspection OldTargetApi
targetSdkVersion 27
multiDexEnabled true
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
externalNativeBuild {
cmake {
cppFlags "-std=c++14 -frtti -fexceptions"
}
}
// multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion '27.0.3'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
implementation 'com.google.dagger:dagger-android:2.16'
implementation 'com.google.dagger:dagger-android-support:2.16'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation(
[group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.6'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.6'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.6']
)
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-java8:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
compileOnly 'org.projectlombok:lombok:1.18.2'
annotationProcessor 'org.projectlombok:lombok:1.18.2'
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
implementation "android.arch.persistence.room:rxjava2:$room_version"
implementation "android.arch.persistence.room:guava:$room_version"
testImplementation "android.arch.persistence.room:testing:$room_version"
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "android.arch.lifecycle:runtime:$lifecycle_version"
implementation "android.arch.lifecycle:common-java8:$lifecycle_version"
implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"
testImplementation "android.arch.core:core-testing:$lifecycle_version"
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:support-compat:27.1.1'
implementation 'com.android.support:exifinterface:27.1.1'
implementation 'com.android.support:multidex:1.0.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
更新:
我做了@Ben-P 在他的回答中建议的。但是现在我的 RecycleViewAdapter 给我错误。
我在这条线上得到一个 IllegalArgumentException - Target must be null
: picasso.load(countriesFullEntity.getFlag()).into(holder.countryIcon);
in onBindViewHolder(holder,position)
在您的片段中,您有以下代码:
private void initialize() { this.countriesEntireHolderRV = new RecyclerView(this.getContext()); ... }
早些时候,在 onCreateView()
中,您调用 ButterKnife.bind()
... 这意味着上面的代码正在将 countriesEntireHolderRV
重新分配给新的引用。这个新的 RecyclerView 永远不会添加到您的 Fragment 视图中(并且可能无论如何都不可见,因为 LinearLayout 已经有一个 child 占据了所有可见的 space),所以您的所有操作都与用户看不到的 RecyclerView。
只需从 initialize()
中删除该行即可。然后你将在你的布局文件中使用 RecyclerView,一切都会出现。