I have created an AsyncTaskLoader for loading JSON data from an API. However, I noticed that if I rotate the screen of my device, it tries to load the data from the API again.
As a result, if I turn off my Internet connection and rotate the screen, the loader fails to return data since the HTTP request fails.
// NewsLoader.java
public class NewsLoader extends AsyncTaskLoader<String> {
private String url;
public NewsLoader(#NonNull Context context, String url) {
super(context);
this.url = url.trim();
}
#Override
protected void onStartLoading() {
forceLoad();
}
#Override
public String loadInBackground() {
if (url == null || url.isEmpty()) return null;
return NetworkUtils.fetchNews(url);
}
}
Then,
// NewsActivity.java
// Initialising the loader
LoaderManager.getInstance(this).initLoader(LOADER_ID, args, this);
// onCreateLoader method
public Loader<String> onCreateLoader(int id, #Nullable Bundle args) {
// Process args and get url
return new NewsLoader(this, url);
}
As far as I know, this isn't normal behaviour for a loader. Any idea what is wrong?
I eventually figured out what the problem was. The data has to be cached manually, intercepting the loaded data within deliverResult() and saving it in an instance variable for later use.
Here is the updated code.
// NewsLoader.java
public class NewsLoader extends AsyncTaskLoader<String> {
private String url;
private String cachedData = null;
public NewsLoader(#NonNull Context context, String url) {
super(context);
this.url = url.trim();
}
#Override
protected void onStartLoading() {
if (cachedData != null) deliverResult(cachedData);
else forceLoad();
}
#Override
public String loadInBackground() {
if (url == null || url.isEmpty()) return null;
return NetworkUtils.fetchNews(url);
}
#Override
public void deliverResult(String data) {
cachedData = data;
super.deliverResult(data);
}
}
Related
I am trying to run a AlertDialog in my flutter plugin. So I need the Activity context. I tried using the Application context. However, I was greeted with this fine error and learned that I must use the Activity context.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
For some reason when I call getActivity() it always returns null. I was wondering if anyone could give me some pointers as to why this is happening. Here is my Plugin class I cleaned it up so it only contains the ActivityAware code. Did I not implement something correctly? Any help would be much appreciated!
public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
private ActivityPluginBinding activityBinding;
private FlutterPluginBinding flutterBinding;
#Override
public void onAttachedToEngine(#NonNull FlutterPluginBinding flutterPluginBinding) {
flutterBinding = flutterPluginBinding;
}
#Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activityBinding = binding;
}
#Override
public void onDetachedFromActivity() {
activityBinding = null;
}
#Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
activityBinding = binding;
}
#Override
public void onDetachedFromActivityForConfigChanges() {
activityBinding = null;
}
// Implementation
public Context getApplicationContext() {
return (flutterBinding != null) ? flutterBinding.getApplicationContext() : null;
}
public Activity getActivity() {
return (activityBinding != null) ? activityBinding.getActivity() : null;
}
}
Those objects might have cleared by time they are accessed. You can keep WeakReference to Application context and Activity context and access them later. This way you will avoid memory leak as well.
Something like
private WeakReference<Context> weakApplicationContext;
private WeakReference<Activity> weakActivity;
and then
#Override
public void onAttachedToEngine(#NonNull FlutterPluginBinding flutterPluginBinding) {
weakApplicationContext = new WeakReference<>(flutterPluginBinding.getApplicationContext());
}
#Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
weakActivit = new WeakReference<>(binding.getActivity());
}
Then access them as
public Activity getActivity() {
return weakActivity.get();
}
I'm implementing the paging library 2.1.2 in my Android app, my DataSource class looks like this
public class TransactionsDataSource extends PageKeyedDataSource<Integer, Items> {
private static final int FIRST_PAGE = 1;
private Context context;
private Repository repository;
private String bearerToken;
private int merchantId;
public TransactionsDataSource(Context context, int merchantId) {
this.context = context;
this.merchantId = merchantId;
repository = new Repository();
repository.initLoginSharedPref(context);
bearerToken = repository.getAccessToken();
}
#Override
public void loadInitial(#NonNull PageKeyedDataSource.LoadInitialParams<Integer> params, #NonNull PageKeyedDataSource.LoadInitialCallback<Integer, Items> callback) {
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, FIRST_PAGE)
.enqueue(new Callback<MainResponse>() {
#Override
public void onResponse(Call<MainResponse> call, Response<MainResponse> response) {
if (response.body() != null){
int code = response.body().getCode();
if (code == SERVER_OK_CODE){
callback.onResult(response.body().getData().getItems(), null, FIRST_PAGE + 1);
} else if (code == SERVER_UNAUTHENTICATED_CODE){
repository.clearCache();
HelperMethods.signOut(context);
} else {
//no more data to load
}
}
}
#Override
public void onFailure(Call<MainResponse> call, Throwable t) {
}
});
}
#Override
public void loadBefore(#NonNull PageKeyedDataSource.LoadParams<Integer> params, #NonNull PageKeyedDataSource.LoadCallback<Integer, Items> callback) {
}
#Override
public void loadAfter(#NonNull PageKeyedDataSource.LoadParams<Integer> params, #NonNull PageKeyedDataSource.LoadCallback<Integer, Items> callback) {
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, params.key)
.enqueue(new Callback<MainResponse>() {
#Override
public void onResponse(Call<MainResponse> call, Response<MainResponse> response) {
if (response.body() != null){
int code = response.body().getCode();
if (code == SERVER_OK_CODE){
Integer key = params.key + 1;
callback.onResult(response.body().getData().getItems(), key);
} else if (code == SERVER_UNAUTHENTICATED_CODE){
repository.clearCache();
HelperMethods.signOut(context);
} else {
//no more data to load
}
}
}
#Override
public void onFailure(Call<MainResponse> call, Throwable t) {
}
});
}
}
As per my knowledge the loadInitial() method is reponsible for loading the first page of data and then loadAfter() will be triggered after that to load the rest of the pages while the key is incremented by one until there's no more data to be loaded. But when I debug the app the loadAfter() is continously repeat loading the first page only even I make sure to increment the params.key() by 1. I even tried to hard code the page number as following but still loading the first page only
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, 2)
I need to know what's happening and how to fix this bug?
Although my implementation is 100% correct and After I tried so many things finally I got my way to solve this weird behavior. I made a small change in my interface instead of passing the page number to getMerchantTransactions() I decided to send the whole url as a parameter and it worked fine
here's my method in interface after update
#POST
#FormUrlEncoded
Call<MainResponse> getMerchantTransactions(#Header("Authorization") String bearerToken,
#Field("id") int merchantId,
#Url String url);
And then calling this method in loadInitial() and loadAfter() like this
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, "myUrl?pagenumber=" + params.key)
I am developing an application with the help of Model View Presenter pattern.
I make use of Retrofit and so I have a ApiClient and ApiInterface with endpoints. I implement the interface in a RemoteDataSource class which I call in the Repository class.
My questions is - how do I make use of an Interactor class to make the repository communicate with the Presenter?
Here is my code until now:
ApiInterface
public interface ApiInterface {
#GET("?")
Call<ArrayList<Movie>> getMoviesByTitle(#Query("t") String title,#Query("apiKey") String apiKey);
}
RemoteDataSource class
private static MovieRemoteDataSource instance;
private final ApiInterface service;
public MovieRemoteDataSource(ApiInterface movieApi) {
service = ApiClient.createService(ApiInterface.class);
}
public static MovieRemoteDataSource getInstance(ApiInterface movieApi) {
if (instance == null) {
instance = new MovieRemoteDataSource(movieApi);
}
return instance;
}
#Override
public void getMovies(String title, String apiKey, final LoadMovieCallBack callback) {
service.getMoviesByTitle(title,apiKey).enqueue(new Callback<ArrayList<Movie>>() {
#Override
public void onResponse(Call<ArrayList<Movie>> call, Response<ArrayList<Movie>> response) {
ArrayList<Movie> movies = response.body();// != null ? //response.body().getTitle() : null;
if (movies != null && !movies.isEmpty()) {
callback.onMoviesLoaded(movies);
} else {
callback.onDataNotAvailable();
}
}
#Override
public void onFailure(Call<ArrayList<Movie>> call, Throwable t) {
callback.onError();
}
});
}
DataSource interface with a callback
public interface MovieDataSource {
interface LoadMovieCallBack{
void onMoviesLoaded(ArrayList<Movie> movies);
void onDataNotAvailable();
void onError();
}
void getMovies(String title, String apiKey,LoadMovieCallBack callback);
}
Repository
private MovieRemoteDataSource movieRemoteDataSource;
public MoviesRepository() {//ApiInterface movieApi) {
//this.service = ApiClient.createService(ApiInterface.class);
}
public static MoviesRepository getInstance(ApiInterface service) {
if (instance == null) {
instance = new MoviesRepository();
}
return instance;
}
public void getMovies(String title, String apiKey ) {
movieRemoteDataSource.getMovies(title,apiKey,this);
}
In MoviesRepository you should declare a function with Callback. Your Presenter
should implement MovieDataSource.LoadMovieCallBack and pass it when you call MoviesRepository
public void getMovies(String title, String apiKey,MovieDataSource.LoadMovieCallBack callback) {
movieRemoteDataSource.getMovies(title,apiKey,callback);
}
Here is Google MVP already done for todo app sample, you can refer it. But now it deprecated because Google recommends MVVM
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 have a list of urls and I want to hit them all in parallel and combine the result into a final Java object using Java spring RestTemplate. I'm able to achieve it buy accessing the urls in sequence, but due to performance concerns, I want to achieve them same in parallel. Looking forward to hearing your suggestions
You can use threads to perform parallel jobs.
First, make a result data class to handle the responses of your URLs
public class URLResult {
public String url;
public String response;
public Date responseTime;
// Add fields whatever you need
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public Date getResponseTime() {
return responseTime;
}
public void setResponseTime(Date responseTime) {
this.responseTime = responseTime;
}
}
Then use it in your threads :
public List<URLResult> list = new ArrayList<>();
public synchronized void addToList(URLResult result) {
list.add(result);
}
public void hitUrl(String url) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
URLResult result = new URLResult();
//here, connect to your url, get the result then set your URLResult fields
addToList(result);
}
});
thread.start();
}
At the end of the process, you will have a "list" of your results.