Dynamic parameters in use cases.

This commit is contained in:
Fernando Cejas
2016-12-23 15:22:42 -03:00
parent 988e02b25b
commit dfd61c2917
18 changed files with 233 additions and 69 deletions

View File

@@ -22,6 +22,7 @@ ext {
gsonVersion = '2.3'
okHttpVersion = '2.5.0'
androidAnnotationsVersion = '21.0.3'
arrowVersion = '1.0.0'
//Testing
robolectricVersion = '3.1.1'
@@ -42,7 +43,7 @@ ext {
recyclerView: "com.android.support:recyclerview-v7:${recyclerViewVersion}",
rxJava: "io.reactivex:rxjava:${rxJavaVersion}",
rxAndroid: "io.reactivex:rxandroid:${rxAndroidVersion}",
javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}"
]
presentationTestDependencies = [
@@ -57,11 +58,13 @@ ext {
javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
rxJava: "io.reactivex:rxjava:${rxJavaVersion}",
arrow: "com.fernandocejas:arrow:${arrowVersion}"
]
domainTestDependencies = [
junit: "junit:junit:${jUnitVersion}",
mockito: "org.mockito:mockito-core:${mockitoVersion}",
assertj: "org.assertj:assertj-core:${assertJVersion}"
]
dataDependencies = [

View File

@@ -23,7 +23,9 @@ dependencies {
compile domainDependencies.javaxInject
compile domainDependencies.rxJava
compile domainDependencies.arrow
testCompile domainTestDependencies.junit
testCompile domainTestDependencies.mockito
}
testCompile domainTestDependencies.assertj
}

View File

@@ -19,6 +19,8 @@ import com.fernandocejas.android10.sample.domain.User;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import com.fernandocejas.arrow.annotations.VisibleForTesting;
import com.fernandocejas.arrow.optional.Optional;
import javax.inject.Inject;
import rx.Observable;
@@ -29,19 +31,26 @@ import rx.Observable;
public class GetUserDetails extends UseCase {
public static final String NAME = "userDetails";
public static final String PARAM_USER_ID_KEY = "userId";
@VisibleForTesting
static final int PARAM_USER_ID_DEFAULT_VALUE = -1;
private final int userId;
private final UserRepository userRepository;
@Inject
public GetUserDetails(int userId, UserRepository userRepository,
ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
public GetUserDetails(UserRepository userRepository, ThreadExecutor threadExecutor,
PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
this.userId = userId;
this.userRepository = userRepository;
}
@Override protected Observable buildUseCaseObservable() {
return this.userRepository.user(this.userId);
@Override protected Observable buildUseCaseObservable(Optional<Params> params) {
if (params.isPresent()) {
final int userId = params.get().getInt(PARAM_USER_ID_KEY, PARAM_USER_ID_DEFAULT_VALUE);
return this.userRepository.user(userId);
} else {
return Observable.empty();
}
}
}

View File

@@ -19,6 +19,7 @@ import com.fernandocejas.android10.sample.domain.User;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import com.fernandocejas.arrow.optional.Optional;
import javax.inject.Inject;
import rx.Observable;
@@ -39,7 +40,7 @@ public class GetUserList extends UseCase {
this.userRepository = userRepository;
}
@Override public Observable buildUseCaseObservable() {
@Override public Observable buildUseCaseObservable(Optional<Params> params) {
return this.userRepository.users();
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (C) 2016 Fernando Cejas Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fernandocejas.android10.sample.domain.interactor;
import java.util.HashMap;
import java.util.Map;
/**
* Class backed by a Map, used to pass parameters to {@link UseCase} instances.
*/
public final class Params {
public static final Params EMPTY = Params.create();
private final Map<String, Object> parameters = new HashMap<>();
private Params() {}
public static Params create() {
return new Params();
}
public void putInt(String key, int value) {
parameters.put(key, value);
}
int getInt(String key, int defaultValue) {
final Object object = parameters.get(key);
if (object == null) {
return defaultValue;
}
try {
return (int) object;
} catch (ClassCastException e) {
return defaultValue;
}
}
}

View File

@@ -17,8 +17,9 @@ package com.fernandocejas.android10.sample.domain.interactor;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import rx.Subscriber;
import com.fernandocejas.arrow.optional.Optional;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
@@ -38,8 +39,7 @@ public abstract class UseCase {
private Subscription subscription = Subscriptions.empty();
protected UseCase(ThreadExecutor threadExecutor,
PostExecutionThread postExecutionThread) {
protected UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
this.threadExecutor = threadExecutor;
this.postExecutionThread = postExecutionThread;
}
@@ -47,17 +47,18 @@ public abstract class UseCase {
/**
* Builds an {@link rx.Observable} which will be used when executing the current {@link UseCase}.
*/
protected abstract Observable buildUseCaseObservable();
protected abstract Observable buildUseCaseObservable(Optional<Params> params);
/**
* Executes the current use case.
*
* @param useCaseSubscriber The guy who will be listen to the observable build
* with {@link #buildUseCaseObservable()}.
* @param useCaseSubscriber Subscriber which will be listening to the observable build
* by {@link #buildUseCaseObservable(Optional)} ()} method.
* @param params Parameters used to build execute this use case.
*/
@SuppressWarnings("unchecked")
public void execute(Subscriber useCaseSubscriber) {
this.subscription = this.buildUseCaseObservable()
public void execute(Subscriber useCaseSubscriber, Params params) {
this.subscription = this.buildUseCaseObservable(Optional.of(params))
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(useCaseSubscriber);

View File

@@ -18,8 +18,7 @@ package com.fernandocejas.android10.sample.domain;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
public class UserTest {
@@ -34,8 +33,8 @@ public class UserTest {
@Test
public void testUserConstructorHappyCase() {
int userId = user.getUserId();
final int userId = user.getUserId();
assertThat(userId, is(FAKE_USER_ID));
assertThat(userId).isEqualTo(FAKE_USER_ID);
}
}

View File

@@ -18,12 +18,15 @@ package com.fernandocejas.android10.sample.domain.interactor;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import com.fernandocejas.arrow.optional.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import rx.Observable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -31,7 +34,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
@RunWith(MockitoJUnitRunner.class)
public class GetUserDetailsTest {
private static final int FAKE_USER_ID = 123;
private static final int USER_ID = 123;
private GetUserDetails getUserDetails;
@@ -41,15 +44,38 @@ public class GetUserDetailsTest {
@Before
public void setUp() {
getUserDetails = new GetUserDetails(FAKE_USER_ID, mockUserRepository,
mockThreadExecutor, mockPostExecutionThread);
getUserDetails = new GetUserDetails(mockUserRepository, mockThreadExecutor,
mockPostExecutionThread);
}
@Test
public void testGetUserDetailsUseCaseObservableHappyCase() {
getUserDetails.buildUseCaseObservable();
final Params params = Params.create();
params.putInt(GetUserDetails.PARAM_USER_ID_KEY, USER_ID);
verify(mockUserRepository).user(FAKE_USER_ID);
getUserDetails.buildUseCaseObservable(Optional.of(params));
verify(mockUserRepository).user(USER_ID);
verifyNoMoreInteractions(mockUserRepository);
verifyZeroInteractions(mockPostExecutionThread);
verifyZeroInteractions(mockThreadExecutor);
}
@Test
public void testShouldReturnEmptyObservableWhenNoParameters() {
final Observable observable = getUserDetails.buildUseCaseObservable(Optional.<Params>absent());
assertThat(observable).isEqualTo(Observable.empty());
verifyZeroInteractions(mockUserRepository);
verifyZeroInteractions(mockPostExecutionThread);
verifyZeroInteractions(mockThreadExecutor);
}
@Test
public void testShouldUseDefaultUserIdValueWhenNoUserIdParameter() {
getUserDetails.buildUseCaseObservable(Optional.of(Params.create()));
verify(mockUserRepository).user(GetUserDetails.PARAM_USER_ID_DEFAULT_VALUE);
verifyNoMoreInteractions(mockUserRepository);
verifyZeroInteractions(mockPostExecutionThread);
verifyZeroInteractions(mockThreadExecutor);

View File

@@ -18,12 +18,14 @@ package com.fernandocejas.android10.sample.domain.interactor;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import com.fernandocejas.arrow.optional.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -45,11 +47,22 @@ public class GetUserListTest {
@Test
public void testGetUserListUseCaseObservableHappyCase() {
getUserList.buildUseCaseObservable();
getUserList.buildUseCaseObservable(Optional.of(Params.EMPTY));
verify(mockUserRepository).users();
verifyNoMoreInteractions(mockUserRepository);
verifyZeroInteractions(mockThreadExecutor);
verifyZeroInteractions(mockPostExecutionThread);
}
@Test
@SuppressWarnings("unchecked")
public void testThereShouldNotBeAnyInteractionWithParams() {
Optional params = mock(Optional.class);
getUserList.buildUseCaseObservable(params);
verify(mockUserRepository).users();
verifyZeroInteractions(params);
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (C) 2016 Fernando Cejas Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fernandocejas.android10.sample.domain.interactor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class ParamsTest {
private Params params;
@Before
public void setUp() {
params = Params.create();
}
@Test
public void testShouldReturnIntValue() {
params.putInt("key01", 3);
assertThat(params.getInt("key01", 5)).isEqualTo(3);
}
@Test
public void testShouldReturnIntDefaultValue() {
params.putInt("key01", 3);
params.putInt("key02", 4);
assertThat(params.getInt("key03", 5)).isEqualTo(5);
}
}

View File

@@ -17,6 +17,7 @@ package com.fernandocejas.android10.sample.domain.interactor;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.arrow.optional.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,8 +28,7 @@ import rx.Subscriber;
import rx.observers.TestSubscriber;
import rx.schedulers.TestScheduler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@RunWith(MockitoJUnitRunner.class)
@@ -51,35 +51,33 @@ public class UseCaseTest {
TestScheduler testScheduler = new TestScheduler();
given(mockPostExecutionThread.getScheduler()).willReturn(testScheduler);
useCase.execute(testSubscriber);
useCase.execute(testSubscriber, Params.EMPTY);
assertThat(testSubscriber.getOnNextEvents().size(), is(0));
assertThat(testSubscriber.getOnNextEvents().size()).isZero();
}
@Test
public void testSubscriptionWhenExecutingUseCase() {
TestSubscriber<Integer> testSubscriber = new TestSubscriber<>();
useCase.execute(testSubscriber);
useCase.execute(testSubscriber, Params.EMPTY);
useCase.unsubscribe();
assertThat(testSubscriber.isUnsubscribed(), is(true));
assertThat(testSubscriber.isUnsubscribed()).isTrue();
}
private static class UseCaseTestClass extends UseCase {
UseCaseTestClass(
ThreadExecutor threadExecutor,
PostExecutionThread postExecutionThread) {
UseCaseTestClass(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
}
@Override protected Observable buildUseCaseObservable() {
@Override protected Observable buildUseCaseObservable(Optional<Params> params) {
return Observable.empty();
}
@Override public void execute(Subscriber UseCaseSubscriber) {
super.execute(UseCaseSubscriber);
@Override public void execute(Subscriber useCaseSubscriber, Params params) {
super.execute(useCaseSubscriber, Params.EMPTY);
}
}
}

View File

@@ -27,6 +27,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import rx.Subscriber;
import com.fernandocejas.android10.sample.domain.interactor.Params;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
@@ -34,6 +35,8 @@ import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class UserDetailsPresenterTest {
private static final int USER_ID = 1;
private UserDetailsPresenter userDetailsPresenter;
@Mock private Context mockContext;
@@ -51,10 +54,10 @@ public class UserDetailsPresenterTest {
public void testUserDetailsPresenterInitialize() {
given(mockUserDetailsView.context()).willReturn(mockContext);
userDetailsPresenter.initialize();
userDetailsPresenter.initialize(USER_ID);
verify(mockUserDetailsView).hideRetry();
verify(mockUserDetailsView).showLoading();
verify(mockGetUserDetails).execute(any(Subscriber.class));
verify(mockGetUserDetails).execute(any(Subscriber.class), any(Params.class));
}
}

View File

@@ -17,6 +17,7 @@ package com.fernandocejas.android10.sample.test.presenter;
import android.content.Context;
import com.fernandocejas.android10.sample.domain.interactor.GetUserList;
import com.fernandocejas.android10.sample.domain.interactor.Params;
import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter;
import com.fernandocejas.android10.sample.presentation.view.UserListView;
@@ -55,6 +56,6 @@ public class UserListPresenterTest {
verify(mockUserListView).hideRetry();
verify(mockUserListView).showLoading();
verify(mockGetUserList).execute(any(Subscriber.class));
verify(mockGetUserList).execute(any(Subscriber.class), any(Params.class));
}
}

View File

@@ -32,14 +32,8 @@ import javax.inject.Named;
@Module
public class UserModule {
private int userId = -1;
public UserModule() {}
public UserModule(int userId) {
this.userId = userId;
}
@Provides @PerActivity @Named(GetUserList.NAME) UseCase provideGetUserListUseCase(
GetUserList getUserList) {
return getUserList;
@@ -48,6 +42,6 @@ public class UserModule {
@Provides @PerActivity @Named(GetUserDetails.NAME) UseCase provideGetUserDetailsUseCase(
UserRepository userRepository, ThreadExecutor threadExecutor,
PostExecutionThread postExecutionThread) {
return new GetUserDetails(userId, userRepository, threadExecutor, postExecutionThread);
return new GetUserDetails(userRepository, threadExecutor, postExecutionThread);
}
}

View File

@@ -22,6 +22,7 @@ import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber;
import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails;
import com.fernandocejas.android10.sample.domain.interactor.UseCase;
import com.fernandocejas.android10.sample.domain.interactor.Params;
import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory;
import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity;
import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
@@ -64,19 +65,19 @@ public class UserDetailsPresenter implements Presenter {
}
/**
* Initializes the presenter by start retrieving user details.
* Initializes the presenter by showing/hiding proper views
* and retrieving user details.
*/
public void initialize() {
this.loadUserDetails();
}
/**
* Loads user details.
*/
private void loadUserDetails() {
public void initialize(int userId) {
this.hideViewRetry();
this.showViewLoading();
this.getUserDetails();
this.getUserDetails(userId);
}
private void getUserDetails(int userId) {
final Params params = Params.create();
params.putInt(GetUserDetails.PARAM_USER_ID_KEY, userId);
this.getUserDetailsUseCase.execute(new UserDetailsSubscriber(), params);
}
private void showViewLoading() {
@@ -106,10 +107,6 @@ public class UserDetailsPresenter implements Presenter {
this.viewDetailsView.renderUser(userModel);
}
private void getUserDetails() {
this.getUserDetailsUseCase.execute(new UserDetailsSubscriber());
}
@RxLogSubscriber
private final class UserDetailsSubscriber extends DefaultSubscriber<User> {

View File

@@ -22,6 +22,7 @@ import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber;
import com.fernandocejas.android10.sample.domain.interactor.GetUserList;
import com.fernandocejas.android10.sample.domain.interactor.UseCase;
import com.fernandocejas.android10.sample.domain.interactor.Params;
import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory;
import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity;
import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
@@ -113,7 +114,7 @@ public class UserListPresenter implements Presenter {
}
private void getUserList() {
this.getUserListUseCase.execute(new UserListSubscriber());
this.getUserListUseCase.execute(new UserListSubscriber(), Params.EMPTY);
}
private final class UserListSubscriber extends DefaultSubscriber<List<User>> {

View File

@@ -13,7 +13,6 @@ import com.fernandocejas.android10.sample.presentation.R;
import com.fernandocejas.android10.sample.presentation.internal.di.HasComponent;
import com.fernandocejas.android10.sample.presentation.internal.di.components.DaggerUserComponent;
import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent;
import com.fernandocejas.android10.sample.presentation.internal.di.modules.UserModule;
import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment;
/**
@@ -55,7 +54,7 @@ public class UserDetailsActivity extends BaseActivity implements HasComponent<Us
private void initializeActivity(Bundle savedInstanceState) {
if (savedInstanceState == null) {
this.userId = getIntent().getIntExtra(INTENT_EXTRA_PARAM_USER_ID, -1);
addFragment(R.id.fragmentContainer, new UserDetailsFragment());
addFragment(R.id.fragmentContainer, UserDetailsFragment.forUser(userId));
} else {
this.userId = savedInstanceState.getInt(INSTANCE_STATE_PARAM_USER_ID);
}
@@ -65,7 +64,6 @@ public class UserDetailsActivity extends BaseActivity implements HasComponent<Us
this.userComponent = DaggerUserComponent.builder()
.applicationComponent(getApplicationComponent())
.activityModule(getActivityModule())
.userModule(new UserModule(this.userId))
.build();
}

View File

@@ -21,12 +21,14 @@ import com.fernandocejas.android10.sample.presentation.model.UserModel;
import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter;
import com.fernandocejas.android10.sample.presentation.view.UserDetailsView;
import com.fernandocejas.android10.sample.presentation.view.component.AutoLoadImageView;
import com.fernandocejas.arrow.checks.Preconditions;
import javax.inject.Inject;
/**
* Fragment that shows details of a certain user.
*/
public class UserDetailsFragment extends BaseFragment implements UserDetailsView {
private static final String PARAM_USER_ID = "param_user_id";
@Inject UserDetailsPresenter userDetailsPresenter;
@@ -39,6 +41,14 @@ public class UserDetailsFragment extends BaseFragment implements UserDetailsView
@Bind(R.id.rl_retry) RelativeLayout rl_retry;
@Bind(R.id.bt_retry) Button bt_retry;
public static UserDetailsFragment forUser(int userId) {
final UserDetailsFragment userDetailsFragment = new UserDetailsFragment();
final Bundle arguments = new Bundle();
arguments.putInt(PARAM_USER_ID, userId);
userDetailsFragment.setArguments(arguments);
return userDetailsFragment;
}
public UserDetailsFragment() {
setRetainInstance(true);
}
@@ -120,14 +130,23 @@ public class UserDetailsFragment extends BaseFragment implements UserDetailsView
}
/**
* Loads all users.
* Load user details.
*/
private void loadUserDetails() {
if (this.userDetailsPresenter != null) {
this.userDetailsPresenter.initialize();
this.userDetailsPresenter.initialize(currentUserId());
}
}
/**
* Get current user id from fragments arguments.
*/
private int currentUserId() {
final Bundle arguments = getArguments();
Preconditions.checkNotNull(arguments, "Fragment arguments cannot be null");
return arguments.getInt(PARAM_USER_ID);
}
@OnClick(R.id.bt_retry)
void onButtonRetryClick() {
UserDetailsFragment.this.loadUserDetails();