android - 从 fragment 中的 MainActivity 中从 JSON API 获取数据

标签 android android-fragments rx-java mvp android-lifecycle

我的项目使用 MVP 架构和 RxJava 从远程 JSON Api 获取数据。

我有一个 MainActivity,它有 2 个角色。第一个是作为一个 fragment 容器,第二个是从 JSON api 获取数据并将其传输到我的 fragment (我现在只有一个 fragment ,但稍后会有另一个使用相同的数据)。

现在,我正在我的 MainActivity 中获取数据。我试图通过在我的 MainActivity 中调用一个方法(使用解耦接口(interface))从我的 fragment 中获取数据。

问题是我的 fragment 中的数据总是空的,我想这是因为我的 Activity 使我的 fragment 膨胀得如此之快,以至于当我的 fragment 调用我的 Activity 方法来获取数据时,由于没有收到请求,该数据仍然是空的答案还没有,这个请求是使用 RxJava 异步调用的。

所以我想等待加载数据以打开我的 fragment ,或者打开我的 fragment 并等待数据加载到 Activity 中,然后再获取它(向用户显示视觉进度)。问题不在于如何做,而在于何时何地。感谢您的帮助。

我移动了我的 loadData() 方法和事务以在生命周期的不同位置多次打开我的 fragment ,但没有任何效果。现在一切都在 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 pollutionLevels 的地方,它应该包含我试图从 fragment 中获取的数据:

@Override
public void updateData(ViewModel viewModel) {
    this.pollutionData = viewModel;

    pollutionLevels = viewModel.getAllPolluants();

    for(PollutionLevel p : pollutionLevels) {
        Log.d(TAG,p.getName());
    }

}

这是我的 MainActivity 中从我的 fragment 中调用以获取数据的方法:

@Override
public ArrayList<PollutionLevel> getPollutionLevels() {
    return pollutionLevels;
}

在我的 fragment 中,我尝试在 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 接口(interface),尝试应用@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 的回答和建议。这是我结束的代码。请注意,我仍然需要实现缓存管理功能,因为现在对两个 fragment 都进行了 JSON 请求。

因此,我的两个 fragment 都使用了一个公共(public)存储库。 MainActivity 变成了一个 fragment 容器,它不获取任何数据。它甚至没有 MVP 结构,因为我认为它现在没用了。

我的两个 fragment (所以我的两个特征)从 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);
    }
}

我的第一个 fragment 的模型( donut 特征):

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;
    }
}

我的第二个 fragment 的模型(污染级别特征):

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;
    }
}

最佳答案

好吧,您可能遇到了计时问题,model.result 是异步 IO 操作,它将在 Activity 完成时以异步方式更新 Activity 数据,而您正在调用获取数据的 fragment 一旦 fragment 附加了 Activity (当你调用 fragment commit() 而不是 commitNow() 时它仍然是异步的)但是如果你将它与可能的网络调用进行比较model.result 它可能总是更快。

实际上我认为你的方法是错误的,当你在 Rx 中使用响应式(Reactive)方式时,你应该推送数据,最后,你从 Activity 的 fragment 端拉取它,虽然您不知道此数据是否已经可用。
从演示者加载的数据应立即更新 fragment ,这意味着您的 Activity.updateData() 将更新 fragment ,或者我认为更正确的方法是演示者将绑定(bind)到 fragment 本身,因为这是它正在更新的实际 View,因此演示者处的 view.UpdateData() 将直接通知 fragment 。

关于android - 从 fragment 中的 MainActivity 中从 JSON API 获取数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43579833/

相关文章:

java - 如何使用 RxJava2 将多个单词组合成一个句子?

android - RxJava : BehaviorSubject not receiving subsequent emissions from source Observable

system.reactive - 从多个任务中创建一个可观察对象

java - 一起使用 Fragment 和 AppCompatActivity 类

java - Android:如何从 fragment 中的主动行为中获取媒体播放器对象?

android - 支持 Web 和移动设备的单一 AngularJS 代码库

javascript - 需要 Android 或 Iphone 移动设备后退按钮单击事件在 javascript 或 jquery

java - CoordinatorLayout 内的 RecyclerView 内的水平 RecyclerView

android - Android-使用路径获取文件

android - MPAndroidChart - 如何将搜索栏与 x 轴对齐以跳过额外的填充?