johncorser
johncorser

Reputation: 9812

How to structure my app using MVP with rxjava and retrofit to get data out of Observables?

So I'll try to keep this question as to-the-point as possible, but it will involve code snippets that traverse an entire codepath.

For context, I am fairly new and completely self-taught for Android dev, so please notify me of any clear misunderstandings/poor organization throughout. The main focus of the question is bug I am experiencing now, which is that, after a network request, the variable that was supposed to be set as a result of that network request is null, because the code moved forward before the network request completed.

Here is my activity method. It is supposed to populate the mFriends variable with the result of mUserPresenter.getUserList(), which is (unfortunately) null:

/**
 * Grabs a list of friends, populates list with UserAdapter
 */
@Override
public void onResume(){
    super.onResume();
    mUserPresenter = new UserPresenter();
    mFriends = mUserPresenter.getUserList();
    if (mGridView.getAdapter() == null) {
        UserAdapter adapter = new UserAdapter(getActivity(), mFriends);
        mGridView.setAdapter(adapter);
    }
    else{
        ((UserAdapter)mGridView.getAdapter()).refill(mFriends);
    }

}

Here is how I am structuring my UserPresenter method getUserList:

public List<User> getUserList()
{
    ApiService.get_friends(this);
    return mUserList;
}

The real magic happens in the ApiService class:

public static void get_friends(final UserPresenter userPresenter){
    ApiEndpointInterface apiService = prepareService();
    apiService.get_friends().
        observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            new Action1<List<User>>()
            {
               @Override
               public void call(List<User> users) {
                  userPresenter.setList(users);
               }
            }
        );
}

My thinking was, that by calling userPresenter.setList(users) in ApiService, that would set mUserList to the response from the api request. However, instead, mUserList == null at the time that getUserList responds.

Any ideas of how I can structure this?

Upvotes: 4

Views: 2391

Answers (1)

Froyo
Froyo

Reputation: 18477

I have also started to learn something similar. Here, I would rather use callbacks.

In your presenter,

public void setList(List<User> users) {
    yourView.setUserList(users);
}

And your activity which implements a view (MVP)

@Override
public void setUserList(List<User> users) {
    ((UserAdapter)mGridView.getAdapter()).refill(mFriends);
}

Also, check that retrofit is not returning null list.

I have a made a small app when I was learning about all this. It fetches user data from GitHub and shows in a list. I was also working with ORMLite and Picasso so some db stuff is there. Dagger Dependency is also used (but you can ignore that). Here's the link.

Here's how my Presenter behaves:

private DataRetrieverImpl dataRetriever;

@Override
public void getUserList(String name) {
    dataRetriever.getUserList(name);
}

@Override
public void onEvent(DataRetrieverEvent event) {
    UserList userList = (UserList)event.getData();
    mainView.setItems(userList);
}

DataRetrieverImpl works as a module (sort of).

private DataRetriever dataRetriever; 
restAdapter = new RestAdapter.Builder().setEndpoint(SERVER_END_POINT).build();
dataRetriever = restAdapter.create(DataRetriever.class);

public void getUserList(final String name) {
    Log.i(TAG, "getting user list for: " + name);

    Observable<UserList> observable = dataRetriever.getUserList(name);

    Log.i(TAG, "subscribe to get userlist");
    observable.subscribe(new Action1<UserList>() {
        @Override
        public void call(UserList userList) {

            eventBus.post(new DataRetrieverEvent("UserList", userList));

            // save to database
            for (User user : userList.getItems()) {
                Log.i(TAG, user.getLogin());
                try {
                    dbHelper.create(user);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {
            throwable.printStackTrace();
        }
    });
}

And DataRetriever is interface for retrofit. I'm sorry for the naming confusion.

public interface DataRetriever {

    @GET("/search/users")
    public Observable<UserList> getUserList(@Query("q") String name);

}

Any my Activity,

@Override
public void setItems(final UserList userList) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            UserAdapter userAdapter = (UserAdapter)recyclerView.getAdapter();
            userAdapter.setUserList(userList);
            userAdapter.notifyItemRangeInserted(0, userAdapter.getItemCount());
        }
    });
}

Upvotes: 2

Related Questions