我的项目使用MVP架构和RxJava从远程JSON Api获取数据。
我有一个MainActivity,它有两个角色。第一个是片段容器,第二个是从JSON api获取数据并将其传输到我的片段(我现在只有一个片段,但稍后将使用相同的数据另外一个片段)。
目前,我正在使用MainActivity获取数据。我试图通过调用MainActivity中的方法(使用接口进行解耦)从我的片段中获取数据。
问题是我的片段中的数据总是空的,我想这是因为我的活动如此快地膨胀我的片段,当我的片段调用我的activity方法来获取数据时,这个数据仍然是空的,因为请求没有收到答案是,使用RxJava异步调用此请求。
所以我想等待加载的数据打开我的片段,或者打开我的片段并在获取之前等待活动中加载的数据(向用户显示可视化进度)。问题不在于如何做到这一点,而是何时何地。谢谢您的帮助。
我移动了我的loadData()方法和事务,在生命周期的不同位置多次打开我的片段,没有任何效果。现在一切都在MainActivity.onStart()中:
@Override
protected void onStart() {
super.onStart();
presenter.setView(this);
// Load data from JSON API
presenter.loadData(city, authToken);
// Load fragments
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
fm.beginTransaction()
.add(R.id.ll_container, fragment)
.commit();
}
}
在我的演示者的loadData()方法中检索数据:
public class MainPresenter implements MainActivityMVP.Presenter {
final static String TAG = MainPresenter.class.getCanonicalName();
private MainActivityMVP.View view;
private MainActivityMVP.Model model;
private Subscription subscription = null;
public MainPresenter(MainActivityMVP.Model model) { this.model = model; }
@Override
public void loadData(String city, String authToken) {
subscription = model.result(city, authToken)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Aqicn>() {
@Override
public void onCompleted() {
Log.i(TAG,"completed");
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Aqicn aqicn) {
Data data = aqicn.getData();
Iaqi iaqi = data.getIaqi();
ViewModel viewModel = new ViewModel(data.getAqi(),
data.getDominentpol(),
iaqi.getCo().getV(),
iaqi.getH().getV(),
iaqi.getNo2().getV(),
iaqi.getO3().getV(),
iaqi.getP().getV(),
iaqi.getPm10().getV(),
iaqi.getPm25().getV(),
iaqi.getR().getV(),
iaqi.getSo2().getV(),
iaqi.getT().getV(),
iaqi.getW().getV());
Log.d(TAG,data.getCity().getName());
if (view != null) {
view.updateData(viewModel);
}
}
});
}
@Override
public void rxUnsubscribe() {
if (subscription != null) {
if (!subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
}
@Override
public void setView(MainActivityMVP.View view) {
this.view = view;
}
}
当收到对请求的响应时,演示者调用MainActivity中的updateData()方法(参见上面的演示者代码)。这是我初始化ArrayList污染等级的地方,它应该包含我试图从我的片段中获取的数据:
@Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
pollutionLevels = viewModel.getAllPolluants();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
}
这是我的MainActivity中从我的片段调用以获取数据的方法:
@Override
public ArrayList<PollutionLevel> getPollutionLevels() {
return pollutionLevels;
}
在我的片段中,我尝试在onAttach()中获取数据,但它始终为空:
public interface PollutionLevelsListener{
ArrayList<PollutionLevel> getPollutionLevels();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
pollutionLevelsListener = (PollutionLevelsListener) context;
ArrayList<PollutionLevel> levels = pollutionLevelsListener.getPollutionLevels();
for(PollutionLevel l:levels) {
Log.d(TAG,l.getName());
}
} catch (ClassCastException castException){
castException.printStackTrace();
}
}
编辑:添加ViewModel.getAllPolluants()方法
这是我的ViewModel中返回ArrayList的方法:
public ArrayList<PollutionLevel> getAllPolluants() {
ArrayList<PollutionLevel> allLevels = new ArrayList();
allLevels.add(new PollutionLevel("Co",Double.toString(co)));
allLevels.add(new PollutionLevel("H",Double.toString(h)));
allLevels.add(new PollutionLevel("No2",Double.toString(no2)));
allLevels.add(new PollutionLevel("o3",Double.toString(o3)));
allLevels.add(new PollutionLevel("p",Double.toString(p)));
allLevels.add(new PollutionLevel("o3",Double.toString(o3)));
allLevels.add(new PollutionLevel("pm10",Integer.toString(pm10)));
allLevels.add(new PollutionLevel("pm25",Integer.toString(pm25)));
allLevels.add(new PollutionLevel("r",Double.toString(r)));
allLevels.add(new PollutionLevel("so2",Double.toString(so2)));
allLevels.add(new PollutionLevel("t",Double.toString(t)));
allLevels.add(new PollutionLevel("w",Double.toString(w)));
return allLevels;
}
编辑:添加新修改的MainActivity类和PollutionLevelListener接口,尝试应用@ cricket_007答案
public class MainActivity extends AppCompatActivity implements MainActivityMVP.View, PollutionLevelsListener {
final static String TAG = MainActivity.class.getCanonicalName();
@BindString(R.string.city)
String city;
@BindString(R.string.aqicn_token)
String authToken;
@Inject
MainActivityMVP.Presenter presenter;
ArrayList<PollutionLevel> pollutionLevels;
PollutionLevelsListener pollutionListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
((App) getApplication()).getComponent().injectPollutionLevels(this);
}
@Override
public void updateData(ViewModel viewModel) {
pollutionLevels = viewModel.getAllPolluants();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
//===== NullPointerException
pollutionListener.onPollutionLevelsLoaded(pollutionLevels);
}
@Override
protected void onStart() {
super.onStart();
presenter.setView(this);
presenter.loadData(city, authToken);
}
@Override
public void onPollutionLevelsLoaded(List<PollutionLevel> levels) {
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
};
// Load fragments
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
fm.beginTransaction()
.add(R.id.ll_container, fragment)
.commit();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.rxUnsubscribe();
}
}
接口
public interface PollutionLevelsListener {
void onPollutionLevelsLoaded(List<PollutionLevel> levels);
}
####################编辑####################### #
在对采用什么解决方案有很多疑问后,我会按照@yosriz的回答和建议进行操作。这是我结束的代码。请注意,我仍然需要实现缓存管理功能,因为现在为两个片段都进行了JSON resquest。
因此,我的两个片段都使用了一个公共存储库。 MainActivity只是一个片段容器,它不会获得任何数据。它甚至没有MVP结构,因为我觉得它现在没用了。
我的两个片段(所以我的两个功能)从PollutionLevelRepository
获取数据:
public interface Repository {
Observable<Aqicn> getPollutionLevelsFromNetwork(String city, String authToken);
Observable<Aqicn> getPollutionLevels(String city, String authToken);
}
public class PollutionLevelsRepository implements Repository {
private PollutionApiService pollutionApiService;
private static Observable<Aqicn> pollutionData = null;
public PollutionLevelsRepository(PollutionApiService pollutionApiService) {
this.pollutionApiService = pollutionApiService;
}
@Override
public Observable<Aqicn> getPollutionLevelsFromNetwork(String city, String authToken) {
pollutionData = pollutionApiService.getPollutionObservable(city, authToken);
return pollutionData;
}
@Override
public Observable<Aqicn> getPollutionLevels(String city, String authToken) {
return getPollutionLevelsFromNetwork(city, authToken);
}
}
我的第一个片段模型(甜甜圈功能):
public class DonutModel implements DonutFragmentMVP.Model {
final static String TAG = DonutModel.class.getSimpleName();
private Repository repository;
public DonutModel(Repository repository) {
this.repository = repository;
}
@Override
public Observable<Aqicn> getPollutionLevels(String city, String authToken) {
Observable<Aqicn> aqicnObservable = repository.getPollutionLevels(city, authToken);
return aqicnObservable;
}
}
我的第二个片段的模型(污染等级特征):
public class PollutionLevelsModel implements PollutionLevelsFragmentMVP.Model {
private Repository repository;
public PollutionLevelsModel(Repository repository) {
this.repository = repository;
}
@Override
public Observable<Aqicn> result(String city, String authToken) {
Observable<Aqicn> aqicnObservable = repository.getPollutionLevels(city, authToken);
return aqicnObservable;
}
}
答案 0 :(得分:1)
我正在尝试通过调用MainActivity中的方法从我的片段中获取数据
看来你的界面只返回字段,很可能在请求完成之前。你似乎明白了......
尚未收到答案,并且使用RxJava
异步调用此请求
我建议你不要等,而是
打开我的片段并在获取之前等待活动中加载的数据(向用户显示可视化进度)。
但是你想要实现它,你可以尝试new ProgressDialog()
并显示/隐藏它。
您的问题是onAttach
立即被调用,请求仍无限期地继续。
您需要从Fragment“订阅”该数据。
通常不会编写“监听器”来实现“getter”,所以让我们重写一下
public interface PollutionLevelsListener {
void onPollutionLevelsLoaded(List<PollutionLevel> levels);
}
然后,您可以使用它来启动片段,而不是在活动开始时立即启动
// The Activity
class ... implements PollutionLevelsListener {
@Override
public void onPollutionLevelsLoaded(List<PollutionLevel> levels) {
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
};
// Moved this section here
// Load fragments
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
// If your object is Parcelable
/*
* Bundle args = new Bundle();
* args.putParcelableArrayList(levels);
* fragment.setArguments(args);
*/
ft.add(R.id.ll_container, fragment).commit();
}
}
现在你有了这个方法,
演示者在MainActivity中调用updateData()方法
嗯,列表来自哪里,所以只需将其传递给然后加载片段的新方法
@Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
if (pollutionLevels == null) {
pollutionsLevels = new ArrayList<>();
}
pollutionLevels.clear();
pollutionLevels.addAll(viewModel.getAllPolluants());
this.onPollutionLevelsLoaded(pollutionsLevels);
}
答案 1 :(得分:1)
好吧,你可能有计时问题,model.result
是异步IO操作,当它完成时将以异步方式更新活动数据,而片段调用以便在片段发生时立即发生数据附加了活动(在调用片段commit()
时仍然是异步的而不是commitNow()
)但是如果将它与可能的model.result
网络调用进行比较,它可能总是更快。
实际上我认为你的方法是错误的,当你使用Rx的反应式时,你应该推送数据,最后,你在{{1}的片段一侧拉动数据},虽然你不知道这些数据是否已经可用
从演示者加载的数据应立即更新片段,这意味着您的Activity
将更新片段,或者更正确的方法是我的观点是演示者将绑定到片段本身,因为这是实际的Activity.updateData()
它正在更新,因此演示者的View
会直接通知片段。
答案 2 :(得分:1)
您是否尝试在片段中创建一个方法,并且一旦调用了updateData(ViewModel viewModel)就可以点击它?
例如(尝试在片段中添加此方法):
public class YourFragmentName extends Fragment {
public YourFragmentName(StepsHandler stepsHandler){
this.stepsHandler = stepsHandler;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_supplier_registrtion_first, container, false);
return rootView;
}
public void dataLoaded() {
// Do what you need after data finish loading..
}
}
来自您的活动:
public class MainActivity extends Activity implements StepsHandler {
YourFragmentName fragmentName;
//onCreate ()
fragmentName = new YourFragmentName(this);
@Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
pollutionLevels = viewModel.getAllPolluants();
fragmentName.dataLoaded();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
}
}