从片段

时间:2017-04-24 04:29:02

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

我的项目使用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;
    }
}

3 个答案:

答案 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());
        }

    }
}