简体   繁体   English

简化三重嵌套循环,以避免在Android Retrofit2中回调地狱(通过RxJava2吗?)

[英]Simplifying triple nested loop to avoid callback hell in android retrofit2 (via RxJava2?)

Context 上下文

Using android retrofit2, need to access deeply nested name strings to fetch and display their Details (where Detail s object has reference to the Group and User objects used to get the Detail ). 使用android retrofit2,需要访问深层嵌套的name字符串以获取并显示其Details (其中Detail的对象引用了用于获取DetailGroupUser对象)。

The JSON consists of a list of Group s that each contain a list of User s that each contain a list of name String s which are captured in these models: JSON由一个Group列表组成,每个Group包含一个User列表,每个User包含一个name String列表,这些name在以下模型中捕获:

public class Group {
    @SerializedName("id")
    public String id;
    @SerializedName("users")
    public List<User> users;
}
public class User {
    @SerializedName("id")
    public String id;
    @SerializedName("detailNames")
    public List<String> detailNames;
}
public class Detail {
    // allow access to objects used to get detail
    public Group group;
    public User user;
    @SerializedName("name")
    public String name;
    @SerializedName("description")
    public String description;
}

The models are populated using the UserApi : 使用UserApi填充模型:

public interface UserApi {
    @GET("groups")
    Call<List<Group>> getGroups();

    @GET("groups/{group_id}/users/{user_id}/details/{detail_name}")
    Call<Detail> getDetail(
            @Path("group_id") String groupId,
            @Path("user_id") String userId,
            @Path("detail_name") String detailName
    );
}

Aim 目标

The aim is to use a given UserApi to make and parse requests to display a Dialog in the format of: 目的是使用给定的UserApi发出和解析请求以显示以下格式的Dialog

Group1 (expandable heading)
    User1 (expandable heading)
        Detail1 (checkbox)
        Detail2 (checkbox)
        ...
Group2 (expandable heading)
    User2 (expandable heading)
        Detail1 (checkbox)
        ...
    ...
...

Problem 问题

The problem is the current solution requests Group s and uses a triple nested for loop to access and fetch Detail s for each name : 问题是当前解决方案请求Group并使用三重嵌套的for循环访问和获取每个name Detail

private void fetchDetails(List<Group> groupList) {
    ArrayList<Group> groups = (ArrayList<Group>) groupList;
    if (groups != null && groups.size() > 0) {
        for (Group group : groups) {
            for (User user: group.users) {
                for (String detailName : user.detailNames) {
                    fetchDetail(group, user, detailName);
                }
            }
        }
    }
}

The problem is worsened since the triple loop makes a request for each name , and is done within the getGroups onResponse callback, which seems unreadable/unmaintainable: 由于三重循环为每个name发出请求,并且在getGroups onResponse回调中完成,因此问题变得更加严重,这似乎onResponse /无法维护:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mUserApi = UserApiClient.getApi();
    fetchGroups();
}
private void fetchGroups() {
    Callback<List<Group>> groupsCall = new Callback<List<Group>>() {
        @Override
        public void onResponse(Call<List<Group>> call, Response<List<Group>> response) {
            int statusCode = response.code();
            switch (statusCode) {
                case HttpURLConnection.HTTP_OK:
                    List<Group> groups = response.body();
                    fetchDetails(groups);
                    break;
            }
        }
        @Override
        public void onFailure(Call<List<Group>> call, Throwable t) {}
    };
    mUserApi.getGroups().enqueue(groupsCall);
}
private void fetchDetail(final Group group, final User user, String detailName) {
    Callback<Detail> detailCallback= new Callback<Detail>() {
        @Override
        public void onResponse(Call<Detail> call, Response<Detail> response) {
            int statusCode = response.code();
            switch (statusCode) {
                case HttpURLConnection.HTTP_OK:
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // display details in ListView
                        }
                    });
                    break;
            }
        }
        @Override
        public void onFailure(Call<Detail> call, Throwable t) {}
    };
    mUserApi.getDetail(group.id, user.id, detailName).enqueue(detailCallback);
}

An RxJava2 solution was suggested to avoid nesting callbacks like the above implementation, but was unfinished due to confusion regarding managing 3 layers of nesting to access the names : 建议使用RxJava2解决方案来避免像上述实现那样嵌套回调,但由于在管理3层嵌套访问names方面存在困惑,因此未完成:

Observable<List<Group>> groupCall =  mUserApi.getGroups();
groupCall.flatMapIterable(x -> x)
   .flatMap(group -> {
       Observable.fromIterable(group.users)
           .flatMap(user -> {
               Observable.fromIterable(user.detailNames)
                   .map(detailName -> {
                        mUserApi.getDetail(group.id, user.id, detailName)
                            .flatMap(detail -> {
                                detail.group = group;
                                detail.user = user;
                                return Observable.just(detail)
                            })
                            .subscribeOn(Schedulers.io())
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe(new Observer<List<Group>>() {
                                @Override
                                public void onSubscribe(Disposable d) {}
                                @Override
                                public void onNext(List<Detail> value) {
                                    mDetails = (ArrayList<Detail>) value;
                                }
                                @Override
                                public void onError(Throwable e) {}
                                @Override
                                public void onComplete() {}
                            });
                   });
           }
   })

There have been some questions (eg, RxJava multiple loop with condition ) that deal with nesting in RxJava but still unsure how to apply these to the deeply nested name s. 存在一些问题(例如, 带有条件的RxJava多循环 )可以处理RxJava中的嵌套,但仍不确定如何将其应用于深度嵌套的name s。

Question

Is it possible to use RxJava2 to avoid callback hell and simplify the triple for loop, is there another way, or should the solution resort to synchronous requests within AsyncTask / AsyncTaskLoader ? 是否可以使用RxJava2避免回调地狱并简化三重for循环,还有其他方法,还是该解决方案应诉诸AsyncTask / AsyncTaskLoader同步请求?

As I mentioned in the comment, I think what you already have is pretty much the simplest form you can get. 正如我在评论中提到的那样,我认为您已经拥有的几乎是最简单的表格。 But it seems you are interested in doing this without the loop so here's a couple of suggestions (but not necessarily better): 但是似乎您有兴趣在没有循环的情况下执行此操作,因此这里有一些建议(但不一定更好):

Method 1: Container Classes 方法1:容器类

If you are willing to create intermediate container classes that can hold group, user, detailname in a single object, you can do something like this: 如果您愿意创建可以在单个对象中容纳组,用户,详细信息名称的中间容器类,则可以执行以下操作:

First, create these container classes: 首先,创建以下容器类:

public class UserWithGroup {
    final Group group;
    final User user;

    public UserWithGroup(Group group, User user) {
        this.group = group;
        this.user = user;
    }
}

public class DetailWithUser {
    final Group group;
    final User user;
    final String detailName;

    public DetailWithUser(Group group, User user, String detailName) {
        this.group = group;
        this.user = user;
        this.detailName = detailName;
    }
}

Then your code with Java 8 Stream can be: 然后,使用Java 8 Stream的代码可以是:

private void fetchDetails(List<Group> groupList) {
    groupList.stream()
            .flatMap(g -> g.users.stream().map(u -> new UserWithGroup(g, u)))
            .flatMap(ug -> ug.user.detailNames.stream().map(n -> new DetailWithUser(ug.group, ug.user, n)))
            .forEach(d -> fetchDetail(d.group, d.user, d.detailName));
}

Or with RxJava: 或使用RxJava:

private void fetchDetails2(List<Group> groupList) {
    Observable.fromIterable(groupList)
            .flatMap(g -> Observable.fromIterable(g.users).map(u -> new UserWithGroup(g, u)))
            .flatMap(ug -> Observable.fromIterable(ug.user.detailNames).map(n -> new DetailWithUser(ug.group, ug.user, n)))
            .flatMap(d -> mUserApi.getDetail(d.group.id, d.user.id, d.detailName)
                    .map(detail -> {
                        detail.group = d.group;
                        detail.user = d.user;
                        return detail
                    }))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(detail -> {
                ...
            });
}

Method2: 方法2:

Android.util.Pair a container class that can hold any two objects. Android.util.Pair一个可以容纳任何两个对象的容器类。 If you use this instead of creating intermediate containers, and you are okay with it, the code can be further simplified. 如果使用此方法而不是创建中间容器,并且可以使用,则可以进一步简化代码。

Java 8 Stream and Pair: Java 8流和配对:

private void fetchDetails3(List<Group> groupList) {
    groupList.stream()
            .flatMap(g -> g.users.stream().map(u -> Pair.create(g, u)))
            .flatMap(p -> p.second.detailNames.stream().map(n -> Pair.create(p, n)))
            .forEach(p -> fetchDetail(p.first.first, p.first.second, p.second));
}

RxJava and Pair: RxJava和配对:

private void fetchDetails4(List<Group> groupList) {
    Observable.fromIterable(groupList)
            .flatMap(g -> Observable.fromIterable(g.users).map(u -> Pair.create(g, u)))
            .flatMap(p -> Observable.fromIterable(p.second.detailNames).map(n -> Pair.create(p, n)))
            .flatMap(p -> fetchDetail2(p.first.first, p.first.second, p.second)
                    .map(detail -> {
                        detail.group = d.group;
                        detail.user = d.user;
                        return detail
                    }))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(detail -> {
                ...
            });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM