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();
}
Related
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);
}
}
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!");
}
}
After using LeakCanary I found that there were many leaks in my app, most of them due to Volley's anonymous callback listeners. So I wrote a Util (below) class which uses static callbacks and WeakReference to keep reference to Context and an anonymous callback. But when I open the app for the first time, i.e. a cold start, the context is GCed soon after the request is made but during a warm start all works fine. Also this happens only for the first activity in the app.
Any alternative way of handling memory leaks with volley which works properly are also welcome.
public abstract class VUtil {
public static final String TAG = VUtil.class.getSimpleName();
public interface JsonCallback {
void onSuccess(JSONObject response);
}
public interface StringCallback {
void onSuccess(String response);
}
public interface ErrorCallback {
void onError(VolleyError error);
}
public static class JsonResponseListener implements Response.Listener<JSONObject> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<JsonCallback> mCallbackWeakReference;
public JsonResponseListener(Context context, JsonCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(JSONObject jsonObject) {
Context context = mContextWeakReference.get();
JsonCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(jsonObject);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class StringResponseListener implements Response.Listener<String> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<StringCallback> mCallbackWeakReference;
public StringResponseListener(Context context, StringCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(String response) {
Context context = mContextWeakReference.get();
StringCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(response);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class ErrorListener implements Response.ErrorListener {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<ErrorCallback> mCallbackWeakReference;
public ErrorListener(Context context, ErrorCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onErrorResponse(VolleyError error) {
Context context = mContextWeakReference.get();
ErrorCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onError(error);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
}
GC depends on many many things that are happening. One possible cause for your case is that when you do your first request after a 'cold boot' you app must init various custom objects, fragments, activities, views caches etc. and thus needs memory before increases the heap and thus do a GC.
The solution I propose however is to change your architecture.
1) it seems that u keep ref to context but it is never used. just drop it
2) you have Volley callbacks which delegates to your custom callbacks which you need to pass anyway, why don't you simply use 1 set of callbacks which you pass to the respective requests.
3) you WeekRef your custom callbacks but u cannot do without them. Week Referencing is not the ultimate solution to memory leaks. you have to find out why the ref is still there when you don't need it.
so if you leak issue is in JsonCallback, StringCallback and ErrorCallback implementations just try to figure this out instead of making the chain longer and cutting it at the end.
Thanks to djodjo's answer which helped me to reach a solution
Always use addToRequestQueue(request, TAG). Here TAG bit is what we'll use to cancel requests when their Activity/Fragment/View or anything is GCed
What i did is create a base activity and add all this request cancellation code in that activity. Here's what it looks like
public abstract class BaseActivity extends AppCompatActivity {
public final String tag;
public BaseActivity() {
super();
tag = getClass().getSimpleName();
}
#Override
protected void onDestroy() {
App.getInstance().cancelRequests(tag);
super.onDestroy();
}
protected <T> void addToRequestQueue(Request<T> request) {
App.getInstance().addToRequestQueue(request, tag);
}
}
cancelRequests is just simple code
getRequestQueue().cancelAll(tag);
Extend your activities from this BaseActivity and use addToRequestQueue to make requests, which will get cancelled automatically when your activity is destroyed. Do similar thing for fragment / dialog / whatever else.
If you make requests from anywhere else which doesn't follow a life-cycle, make sure that it's not binding to any Context and you'll be fine.
I am trying to understand how interfaces work. I have read basic interface tutorials online and watched a few videos so i do have a good idea of what a interface is and its advantages.
Interface
public interface UpyunFormApi {
#Description("上传文件")
#POST("/{bucket}")
#Multipart
public Request upload(#Path("bucket") String bucket, #Part("policy") PolicyPart policy,
#Part("signature") SignaturePart signature, #Part("file") File file, OnRequestListener requestListener,
OnResponseListener<UpyunFormEntity> responseListener, OnErrorListener errorListener);
Code
private UpyunFormApi formApi;
private void uploadAndPushTopic() {
String bucket = UrlManager.getInstance().getUpyunImageBucket();
String secret = UrlManager.getInstance().getUpyunImageSecret();
for (File file : filearr) {
PolicyPart policy = new PolicyPart(bucket);
SignaturePart signature = new SignaturePart(policy, secret);
formApi.upload(bucket, policy, signature, file, uploadRequestListener, uploadResponseListener,
uploadErrorListener);
}
}
private OnRequestListener uploadRequestListener = new OnRequestListener() {
#Override
public void onRequest(Request arg0) {
}
};
private OnErrorListener uploadErrorListener = new OnErrorListener() {
#Override
public void onError(LegolasException arg0) {
}
};
private OnResponseListener<UpyunFormEntity> uploadResponseListener = new OnResponseListener<UpyunFormEntity>() {
#Override
public void onResponse(UpyunFormEntity arg0) {
}
}
};
Why the Responselister works after "formApi.upload()" finished?And I can't find function definition.Help!
I don't understand the code
#Description("上传文件")
#POST("/{bucket}")
#Multipart
Make an interface like :
public interface ChangeItemInterface {
public void doChange(String anyValue);
}
In Adapter,
Intialize interface object like :
ChangeItemInterface changeItemInterface;
In Adapter Constructor,
this.changeItemInterface = context;
In Adapter, From any View Click :
AnyView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
this.changeItemInterface.doChange("AnyValue");
// It will go to the Your Activity Overided method which is explained below this
}
});
In Your Activity implement this interface like :
public class YourActivity extends Activity implements ChangeItemInterface{
/// You'll get override method of your interface, here your call back will come when from adapter click happen
#Override
public void doChange(String anyValue) {
/// Here you can update any value in your activity !
}
}
Hope this demo help you to understand interface use !
I am trying to run the following code:
public class MyListActivity extends ListActivity {
private static class MyListLoadingCallback implements Handler.Callback {
private ListActivity activity;
private MyListManager myListManager;
public MyListLoadingCallback(ListActivity activity,
MyListManager articleListManager) {
this.activity = activity;
this.myListManager = myListManager;
}
#Override
public boolean handleMessage(Message message) {
throw new NotYetImplementedException();
}
}
private MyListManager myListManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myListManager.fetchArticles(new Handler(
new MyListLoadingCallback(this, MyListManager)));
}
I am getting a Null pointer at articleListManager.fetchArticles(new Handler(new MyListLoadingCallback(this, MyListManager)));
My other class looks like the following:
public class MyListManager {
public void fetchArticles(final Handler handler) {
}
}
Can someone please walk me / help me with this snippet of code for better understanding. I don't seem to fully understand it.
The error seems quite clear :).
There is only one "reference" being accessed in the line that's throwing the exception (myListManager.fetchArticles(new Handler(new MyListLoadingCallback(this, MyListManager)));) and it is myListManager, so it must be NULL. And looking at the code, you never initialize it to anything, so it is indeed NULL :)