I am using MVVM pattern in my app I have separate repository class for network operations. In repository class I am getting response from the server. How can I show Toast message send from the server in my main activity.
Below is my code:
Repository.java
public class MyRepository {
MutableLiveData<List<Facts>> mutableLiveData = new MutableLiveData<>();
Application application;
public MyRepository(Application application) {
this.application = application;
}
public MutableLiveData<List<Facts>> getMutableLiveData(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
apiService.getFacts().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Facts>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Facts> facts) {
if(facts.size() > 0 && facts != null){
mutableLiveData.setValue(facts);
}
}
#Override
public void onError(Throwable e) {
TastyToast.makeText(application,e.getMessage(),TastyToast.LENGTH_SHORT,
TastyToast.ERROR).show();
}
#Override
public void onComplete() {
}
});
return mutableLiveData;
}
}
FactsViewModel.java
public class FactsViewModel extends AndroidViewModel {
MyRepository repo;
public FactsViewModel(#NonNull Application application) {
super(application);
repo = new MyRepository(application);
}
public LiveData<List<Facts>> getAllFacts(){
return repo.getMutableLiveData();
}
}
MainActivity.java
private void myFacts(){
FactsViewModel viewModel = new ViewModelProvider(this).get(FactsViewModel.class);
viewModel.getAllFacts().observe(this, new Observer<List<Facts>>() {
#Override
public void onChanged(List<Facts> facts) {
adapter = new FactsAdapter(facts,getActivity());
recycle.setAdapter(adapter);
}
});
}
How can I show error toast messages in MainActivity?
To implement that you firstly need to create a class which has the status of the response ,
Loading which is before the fetching of the data and there you can set progress bar to visible then on success you would set the data to your adapter and right after your hide your progress bar and in the on failure one , you show the toast message error
This is the generic class
class AuthResource<T>(
var authStatus : AuthStatus? = null,
var data : T,
var msg : String? = null
)
fun <T> success(#Nullable data: T): AuthResource<T> {
return AuthResource(
AuthStatus.Success,
data,
null
)
}
fun <T> Error(#NonNull msg: String?, #Nullable data: T) : AuthResource<T>? {
return AuthResource(
AuthStatus.ERROR,
data,
msg
)
}
fun <T> loading(#Nullable data: T): AuthResource<T>? {
return AuthResource(
AuthStatus.LOADING,
data,
null
)
}
enum class AuthStatus {
Success, ERROR, LOADING
}
This is my view model where i implement the authResource with the api response
class MainViewModel #Inject constructor( private var webAuth: WebAuth,
private var favFoodDao: FavFoodDao,
private var application: Application) : ViewModel() {
/// you have to create MediatorLiveData with authresource which contains your modelclass
private var mediatorLiveData = MediatorLiveData<AuthResource<WrapLatestMeals>>()
///Here you return a livedata object
fun ObserverCountries(): LiveData<AuthResource<WrapCountries>> {
var liveData = LiveDataReactiveStreams.fromPublisher(
webAuth.getCountries()
///onerrorreturn , rxjava operator which returns error in case
///of response failure
.onErrorReturn(object : Function<Throwable, WrapCountries> {
override fun apply(t: Throwable): WrapCountries {
var country = WrapCountries()
return country
}
})
.map(object : Function<WrapCountries,
AuthResource<WrapCountries>> {
override fun apply(t: WrapCountries):
AuthResource<WrapCountries> {
if(t.meals.isNullOrEmpty())
{
return Error(
"Error",
t
)!!
}
return success(t)
}
})
.subscribeOn(Schedulers.io())
)
//add that data to mediatorLivedata
mediatorLiveDataCountries.addSource(liveData, Observer {
mediatorLiveDataCountries.postValue(it)
mediatorLiveDataCountries.removeSource(liveData)
})
return mediatorLiveDataCountries
}
This is how you handle the status in your MainActivity
mainViewModel = ViewModelProvider(this,provider)[MainViewModel::class.java]
mainViewModel.ObserverCountries().observe(viewLifecycleOwner, Observer {
when(it.authStatus) {
AuthStatus.LOADING -> /// here you show progressbar in response pre-fetch
{
countriesFragmentBinding.countryprogress.show()
}
AuthStatus.Success -> { // here you update your ui
countriesAdapter = CountriesAdapter(it.data.meals!!,
requireContext())
countriesFragmentBinding.recyclercountries.adapter = countriesAdapter
countriesAdapter!!.deleteCategory(23)
countriesFragmentBinding.countryprogress.hide()
}
AuthStatus.ERROR -> // here you hide your progressbar and show your toast
{
countriesFragmentBinding.countryprogress.hide()
ToastyError(requireContext(),getString(R.string.errorretreivingdata))
}
}
})
return countriesFragmentBinding.root
}
}
Related
I am trying to make an API call with Retrofit 2.9.0 and use the response in a Jetpack Compose activity. The problem is that the call repeats to infinite and updates the UI on every frame. I have never used the retrofit library before so I'm sorry if the code is messy. I will attach a GIF with how the app behaves and code snippets for everything. Thanks for reading this and wanting to help. <3
What is happening in the app
Here is the class that makes the call to the API:
public class ApiHandler {
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.quotable.io/").build();
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
Call<ResponseBody> call = apiInterface.getData();
public void getQuoteRaw()
{
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
MainActivityKt.getModel().setQuoteInfo(response.body().string());
} catch (IOException e)
{
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
System.out.println("ERROR");
}
});
}
The Interface I use to define the getData() method:
public interface ApiInterface {
#GET("random")
Call<ResponseBody> getData();
The ViewModel class for the Compose Activity:
class MainViewModel: ViewModel() {
var quoteInfo by mutableStateOf("")
The Jetpack Compose activity:
val model = MainViewModel()
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
QuoteappTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Greeting()
}
}
}
}
}
#Composable
fun Greeting(viewModel: MainViewModel = model) {
val apihandle = ApiHandler()
apihandle.getQuoteRaw()
Text(text = viewModel.quoteInfo)
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
QuoteappTheme {
Greeting()
}
}
here is my problem:
i have used MVVM/Repository design pattern like this:
Activity -(Observes)-> ViewModel's LiveData -> Repository -> WebService API (GET Resource)
i have another calls for UPDATING Resource to WebService.
Problem:
after changing resource on the server. how i can make the Resource livedata to update itself with new servers data
i want to force it fetch data from server again because some other data may have been changed.
and i dont want to use local database (Room) and change it because my server data might be changed. and they need to fetch each time.
The Only solution passed my Mind was to create a Livedata Source (as dataVersion) to it.
and increment it after every update like this (pseudo code):
dataVersion = new MutableLiveData();
dataVersion.setValue(0);
// my repository get method hasnt anything to do with the dataVersion.
myData = Transformation.switchmap(dataVersion, versionNum -> { WebServiceRepo.getList() });
and how dataVersion should get updated in ViewModel.
You could extend MutableLiveData to give it manual fetch functionality.
public class RefreshLiveData<T> extends MutableLiveData<T> {
public interface RefreshAction<T> {
private interface Callback<T> {
void onDataLoaded(T t);
}
void loadData(Callback<T> callback);
}
private final RefreshAction<T> refreshAction;
private final Callback<T> callback = new RefreshAction.Callback<T>() {
#Override
public void onDataLoaded(T t) {
postValue(t);
}
};
public RefreshLiveData(RefreshAction<T> refreshAction) {
this.refreshAction = refreshAction;
}
public final void refresh() {
refreshAction.loadData(callback);
}
}
Then you can do
public class YourViewModel extends ViewModel {
private RefreshLiveData<List<Project>> refreshLiveData;
private final GithubRepository githubRepository;
private final SavedStateHandle savedStateHandle;
public YourViewModel(GithubRepository githubRepository, SavedStateHandle savedStateHandle) {
this.githubRepository = githubRepository;
this.savedStateHandle = savedStateHandle;
refreshLiveData = Transformations.switchMap(savedStateHandle.getLiveData("userId", ""), (userId) -> {
githubRepository.getProjectList(userId);
});
}
public void refreshData() {
refreshLiveData.refresh();
}
public LiveData<List<Project>> getProjects() {
return refreshLiveData;
}
}
And then repository can do:
public RefreshLiveData<List<Project>> getProjectList(String userId) {
final RefreshLiveData<List<Project>> liveData = new RefreshLiveData<>((callback) -> {
githubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
callback.onDataLoaded(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
}
});
});
return liveData;
}
I am using a paging library from Android Architecture Components.
Paging is implemented using ItemKeyedDataSource
class MyDatasource(
private val queryMap: HashMap<String, String>) : ItemKeyedDataSource<String, Order>() {
private val compositeDisposable: CompositeDisposable by lazy { CompositeDisposable() }
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<Order>) {
compositeDisposable.add(
MyService.getService().fetchData(queryMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<OrdersResponse>() {
override fun onNext(orders: OrdersResponse) {
callback.onResult(orders.data)
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
}
})
)
}
override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<Order>) {
// do nothing
}
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Order>) {
queryMap["offsetOrderId"] = params.key
compositeDisposable.add(
MyService.getService().fetchData(queryMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<OrdersResponse>() {
override fun onNext(orders: OrdersResponse) {
callback.onResult(orders.data)
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
})
)
}
override fun getKey(item: Order): String {
return item.orderId
}
}
I build pagedlist in my viewmodel
class MyViewModel() : ViewModel() {
private var myPagingConfig: PagedList.Config? = null
var dataList: LiveData<PagedList<Order>>? = null
fun getOrders(params: HashMap<String, String>) {
if (myPagingConfig == null) {
myPagingConfig = PagedList.Config.Builder()
.setPageSize(LIMIT)
.setPrefetchDistance(10)
.setEnablePlaceholders(false)
.build()
}
dataList = LivePagedListBuilder(MyDataFactory(
MyDatasource(params)), myPagingConfig!!)
.setInitialLoadKey(null)
.setFetchExecutor(Executors.newFixedThreadPool(5))
.build()
}
}
However, when I observe the dataList in my activity, it sometimes (most of the times) returns an empty list, while in logcat I see that I had fetched data successfully. callback.onResult is invoked after it returns an empty list, but observer never gets notified again.
Can you tell me if what would cause this?
I try to make sample login page with two fields (username, password) and save button with android architecture component, using android data binding, validating the data in viewmodel and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent and EventObserver, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
#Inject
public LoginViewModel(#NonNull AppDatabase appDatabase,
#NonNull JobPortalApplication application,
#NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
#Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver in above case? Any practical example?
Check out below example about how you can create single LiveEvent to observe only one time as LiveData :
Create a class called Event as below that will provide our data once and acts as child of LiveData wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver class like below so that we don't end up placing condition for checking about Event handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event("")); is meant for repository when you've got response from API (Remember to return same LiveData type you've taken in VM instead of SingleLiveEvent class though).
So, let's say you've created LiveData in ViewModel like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
#Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
#Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
I understand that Google gives the guidelines to use LiveData between the ViewModel and UI but there are edge cases where using LiveData as a SingleLiveEvent is like reinventing the wheel. For single time messaging between the view model and user interface we can use the delegate design pattern. When initializing the view model in the activity we just have to set the activity as the implementer of the interface. Then throughout our view model we can call the delegate method.
Interface
public interface Snackable:
void showSnackbarMessage(String message);
UI
public class MyActivity extends AppCompatActivity implements Snackable {
private MyViewModel myViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
this.myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
this.myViewModel.setListener(this);
}
#Override
public void showSnackbarMessage(String message) {
Toast.makeText(this, "message", Toast.LENGTH_LONG).show();
}
}
View Model
public class MyViewModel extends AndroidViewModel {
private Snackable listener;
public MyViewModel(#NonNull Application application) {
super(application);
}
public void setListener(MyActivity activity){
this.listener = activity;
}
private void sendSnackbarMessage(String message){
if(listener != null){
listener.showSnackbarMessage(message);
}
}
private void anyFunctionInTheViewModel(){
sendSnackbarMessage("Hey I've got a message for the UI!");
}
}
I don't have any type of error when I run the application, but the problem is I don't retrieve the data from my interface.
I use Dagger to inject dependencies, and retrofit to retrieve the data from Last.fm Api.
When I try to use a log to check if the data is null, the message is not displayed in logcat and don't work with a toast message.
Service interface
public interface GenreTopTracksService {
#GET("?method=tag.gettoptracks&format=json")
Single<GenreTopTracksResponse> getGenreTopTracks(#Query("tag") String user, #Query("limit") int limit, #Query("api_key") String apiKey);
}
Presenter interface
public interface GenreTopTracksPresenter {
void getGenreTopTracks(String tag, int limit, String apiKey);
}
Interactor interface
public interface GenreTopTracksInteractor {
Single<GenreTopTracksResponse> getGenreTopTracks(String tag, int limit, String apiKey);
}
View interface -- here i have the update data method
public interface GenreTopTracksView {
void updateData(List<Track> tracks);
}
this class is an a implementation of the GenreTopTrackPresneter
public class GenreTopTracksPrensenterImpl implements GenreTopTracksPresenter {
Disposable mDisposable;
GenreTopTracksInteractor mInteractor;
GenreTopTracksView mGenreViewData;
public GenreTopTracksPrensenterImpl(GenreTopTracksInteractor mInteractor, GenreTopTracksView mGenreViewData) {
this.mInteractor = mInteractor;
this.mGenreViewData = mGenreViewData;
}
#Override
public void getGenreTopTracks(String tag, int limit, String apiKey) {
disposeRequest();
mDisposable = mInteractor.getGenreTopTracks(tag, limit, apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Function<GenreTopTracksResponse, List<Track>>() {
#Override
public List<Track> apply(#NonNull GenreTopTracksResponse topTracksResponse) throws Exception {
if (topTracksResponse != null && topTracksResponse.getTopTracks() != null && topTracksResponse.getTopTracks().getTracks() != null) {
return topTracksResponse.getTopTracks().getTracks();
}
return new ArrayList<Track>();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Track>>() {
#Override
public void accept(#NonNull List<Track> tracks) throws Exception {
if (tracks.size() == 0) {
// NO se muestra nada
}else{
mGenreViewData.updateData(tracks);
}
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
Log.d("ERRORGENRE", "Errorxd");
}
});
}
private void disposeRequest() {
if (mDisposable != null && !mDisposable.isDisposed()) {
mDisposable.dispose();
}
}
}
THis is my module class to inject dependencies to my main activity
#Module
public class GenreTopTracksModule {
GenreTopTracksView mView;
public GenreTopTracksModule(GenreTopTracksView view) {
mView = view;
}
// provides the view to create the top tracks presenter
#Singleton
#Provides
public GenreTopTracksView providesTopTracksView() {
return this.mView;
}
// provides a converter factory to create the retrofit instance
#Singleton
#Provides
public Converter.Factory providesConverterFactory() {
return GsonConverterFactory.create();
}
// provides a call adapter factory needed to integrate rxjava with retrofit
#Singleton
#Provides
public CallAdapter.Factory providesCallAdapterFactory() {
return RxJava2CallAdapterFactory.create();
}
// provides a retrofit instance to create the top tracks interactor
#Singleton
#Provides
public Retrofit providesRetrofit(Converter.Factory converter, CallAdapter.Factory adapter) {
return new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(adapter)
.addConverterFactory(converter)
.build();
}
// provides top tracks interactor to make an instance of the presenter
#Singleton
#Provides
public GenreTopTracksInteractor providesTopTopTracksInteractor(Retrofit retrofit) {
return new GenreTopTracksInteractorImplementation(retrofit);
}
// provides top track presenter
#Singleton
#Provides
public GenreTopTracksPresenter providesTopTracksPresenter(GenreTopTracksInteractor interactor, GenreTopTracksView mView) {
return new GenreTopTracksPrensenterImpl(interactor, mView);
}
}
And this is my main activity
public class SelectionActivity extends AppCompatActivity implements GenreTopTracksView{
#Inject
GenreTopTracksPresenter mPresenter;
Button mButton;
EditText mEditText;
String tag;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_selection);
DaggerGenreTopTracksComponent.builder().genreTopTracksModule(new GenreTopTracksModule(this)).build().inject(this);
mButton = (Button) findViewById(R.id.button_prueba);
mEditText = (EditText) findViewById(R.id.edit_prueba);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tag = mEditText.getText().toString();
mPresenter.getGenreTopTracks(tag, Constants.TOP_ITEMS_LIMIT, Constants.API_KEY);
}
});
}
#Override
public void updateData(List<Track> tracks) {
if(tracks != null){
for(int x = 0; x<tracks.size(); x++){
Log.d("datasetTrack", tracks.get(x).getName());
Toast.makeText(SelectionActivity.this, tracks.get(x).getName(), Toast.LENGTH_SHORT).show();
}
}else if(tracks == null){
Log.d("datasetTrack", "datos nulos :(");
Toast.makeText(SelectionActivity.this, "Datos nulos", Toast.LENGTH_SHORT).show();
}
}
}
I need to see the data in the "updateData" method.
First of all recommend you add retrofit client interceptor
Add dependency in build.gradle:
compile 'com.squareup.okhttp3:logging-interceptor:$your_version'
in GenreTopTrackModule change method
#Singleton
#Provides
public Retrofit providesRetrofit(Converter.Factory converter, CallAdapter.Factory adapter) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
return new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(adapter)
.addConverterFactory(converter)
.build();
}
As a result in Logcat you will see response what last.fm api return. If response return right check you implementation