I'm trying to run a simple HTTP query through Volley and this code works completely fine in the default Android MainActivity under onCreate():
JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method.GET, url, (String)null, new Response.Listener<JSONObject>()
{
#Override
public void onResponse(JSONObject response) {
// the response is already constructed as a JSONObject!
try {
// response = response.getJSONObject("results");
String success = response.getString("success"),
status = response.getString("status");
if(status.equals("1"))
{
GlobalVar.armedStatus = 1;
}
else if(status.equals("0"))
{
GlobalVar.armedStatus = 0;
}
System.out.println("Success: "+success+"\nStatus: "+status);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
});
// RequestQueue queue = Volley.newRequestQueue(jsonRequest);
Volley.newRequestQueue(this).add(jsonRequest);
When I run this code in a separate class though, the final line gives an error.
Volley.newRequestQueue(this).add(jsonRequest);
The error on newRequestQueue is "Cannot resolve symbol 'newRequestQueue' and the error on add(jsonRequest); is "Missing method body, or declare abstract."
Any help would be appreciated, not sure why it works in the Activity, but not in the class file... I assume the reason is onCreate(), but I'm sure there is some way to keep it in another class?
It's because in the line:
Volley.newRequestQueue(this).add(jsonRequest);
The this you pass into newRequestQueue() needs to be an Android Context. When you're calling it from your Activity then this refers to the Activity which is a type of Context. When you call if from your own class it will refer to that class which won't be a Context.
You have a few different options:
Add Context as a Constructor Parameter for your Object
You can have it so that when you make an instance of your class you have to pass in a context which you hold onto. This would then be used when you call Volley:
public class MyClass {
private Context context;
public MyClass(Context context) {
this.context = context;
}
public void doVolleyRequest() {
//...
Volley.newRequestQueue(context).add(jsonRequest);
}
}
Then when you make your class from your Activity you would have to instantiate it like:
MyClass myClass = new MyClass(this);
Have a static Reference to the Application Context
You can have a static reference to the Application Context which can be accessed from your Application class. To do this in your Application Class have something like:
public class App extends Application {
private static Context context;
#Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
}
Then from your class where you are using Volley:
Volley.newRequestQueue(App.getContext()).add(jsonRequest);
Have an App Wide RequestQueue
Instead you could set up a request queue in your Application class and add all requests to that:
public class App extends Application {
private static RequestQueue requestQueue;
#Override
public void onCreate() {
super.onCreate();
requestQueue = Volley.newRequestQueue(getApplicationContext());
}
public static RequestQueue getRequestQueue() {
return requestQueue;
}
}
Then in your class:
App.getRequestQueue().add(jsonRequest);
Related
I am trying to achieve MVVM design pattern in my application.I have created viewmodel and repository class but when I am trying to instantiate viewmodel in my MainActivity its showing error red line below MainActivity at the time of instantiation in below line.
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
Below is my code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
PdfViewModel pdfViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
}
}
PdfViewModel.java
public class PdfViewModel extends AndroidViewModel {
private PdfRepository pdfRepository;
public PdfViewModel(#NonNull Application application) {
super(application);
pdfRepository = new PdfRepository(application);
}
public LiveData<List<Pdfs>> getAllPdfs(){
return pdfRepository.getMutableLiveData();
}
}
PdfRepository.java
public class PdfRepository {
private ArrayList<Pdfs> list = new ArrayList<>();
private MutableLiveData<List<Pdfs>> mutableLiveData = new MutableLiveData<>();
private Application application;
public PdfRepository(Application application){
this.application = application;
}
public MutableLiveData<List<Pdfs>> getMutableLiveData(){
SharedPreferences preferences = application.getSharedPreferences("Credentials", Context.MODE_PRIVATE);
String email = preferences.getString("email",null);
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<Pdfs>> call = apiService.getFiles(email);
call.enqueue(new Callback<List<Pdfs>>() {
#Override
public void onResponse(Call<List<Pdfs>> call, Response<List<Pdfs>> response) {
if(response.body() != null){
list = (ArrayList<Pdfs>) response.body();
mutableLiveData.setValue(list);
}
}
#Override
public void onFailure(Call<List<Pdfs>> call, Throwable t) {
TastyToast.makeText(application,t.getMessage(),TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
return mutableLiveData;
}
}
What needs to be corrected in the above code?
Your code is trying to create a new instance of the class ViewModelProvider (with the new keyword) and that's not the right way to instantiate a ViewModel.
On MainActivity, instead of:
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
try:
pdfViewModel = ViewModelProviders.of(this).get(PdfViewModel.class);
Notice the right class is ViewModelProviders (with an "s" at the end) and you need to call the static method of instead of creating a new instance of it with new. If you can't import that class, make sure you have the dependency 'androidx.lifecycle:lifecycle-extensions:2.2.0' added to app/build.gradle.
To make your code even clearer, I'd suggest learning about the Kotlin KTX method viewModels, as described here. You'd need to use Kotlin for that though.
Hi I am trying to get an arraylist of data from a an async task class to another main class:
I was following the answer below but I am a little lost:
How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class?
So I have my class that extends async task and calls to the database to get my object array:
public class GetVideoInfoFromDataBase extends AsyncTask {
// Paginated list of results for song database scan
static PaginatedScanList<AlarmDynamoMappingAdapter> results;
// The DynamoDB object mapper for accessing DynamoDB.
private final DynamoDBMapper mapper;
public interface AlarmsDataBaseAsyncResponse {
void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output);
}
public AlarmsDataBaseAsyncResponse delegate = null;
public GetVideoInfoFromDataBase(AlarmsDataBaseAsyncResponse delegate){
mapper = AWSMobileClient.defaultMobileClient().getDynamoDBMapper();
this.delegate = delegate;
}
#Override
protected Object doInBackground(Object[] params) {
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
results = mapper.scan(AlarmDynamoMappingAdapter.class, scanExpression);
return results;
}
#Override
public void onPostExecute(Object obj) {
delegate.processFinish(results);
}
}
There are no errors but I think I have done something incorrectly in it causing my error.
So in my main activity to call the results I have:
GetVideoInfoFromDataBase asyncTask =new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse(){
#Override
public void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output) {
}
}).execute();
I have two problems here
I am getting the error:
"incompatible types: AsyncTask cannot be converted to GetVideoInfoFromDataBase"
In the mainactivity where i have:
`new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse()`
it wants me to cast it like this:
(GetVideoInfoFromDataBase) new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse()
That doesn't seem right but I thought i would check.
I am not sure how to return the result when overriding the onprocessfinished.
Thanks in advance for your help
First create an Interface
public interface AsyncInterface {
void response(String response);
}
Assign it in the asynctask class as below :-
Context context;
Private AsyncInterface asyncInterface;
AsyncClassConstructor(Context context){
this.context = context;
this.asyncInterface = (AsyncInterface) context;
}
Then inside onPostExecute method of asynctask class :-
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
asyncInterface.response(s);
}
Then implement this interface in your activity :-
class MainActivity extends AppCompatActivity implements AsyncInterface {
and then import the method of asyncInterface
#Override
public void response(String response) {
//Here you get your response
Log.e(TAG, response);
}
Modify Constructor of class.
Need default constructor. By the way, create method to set Interface.
public void setInterface(AlarmsDataBaseAsyncResponse delegate){
this.delegate = delegate;}
In MainActivity, push your logic in:
object.setInterface(new AlarmsDataBaseAsyncResponse(){
#Override
public void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output) {
//your logic
}
});
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.
How do I return a list from AsyncTask to a class instead of a method?
I have this AsyncTask which connects to a webpage, reads the page into a string and then splits it all into variables. This is the page
This is the AsyncTask:
public class sellableIds_AsyncTask extends AsyncTask<String, Void, List> {
String bodyHtml;
List<String> items;
private Context context;
private sellableIdsFilter sellableIdsFilter;
public sellableIds_AsyncTask(Context context) {
this.context = context;
}
public void setSellableIdsFilter(sellableIdsFilter sellableIdsFilter) {
this.sellableIdsFilter = sellableIdsFilter;
}
#Override
protected List doInBackground(String... params) { //Incompatible return type
try {
HttpClient httpClient = new DefaultHttpClient();
HttpGet get = new HttpGet(params[0]);
HttpResponse response = httpClient.execute(get);
bodyHtml = EntityUtils.toString(response.getEntity()).replace("[", "");
;
items = Arrays.asList(bodyHtml.split("\\s*,\\s*"));
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return items;
}
#Override
protected void onPostExecute(List result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
this.sellableIdsFilter.sellableIdsFilter(result);
}
public interface sellableIdsFilter {
public void sellableIdsFilter(List result);
}
}
And called in the activity:
sellableIds_AsyncTask sellableIds_AsyncTask = new sellableIds_AsyncTask(this);
sellableIds_AsyncTask.setSellableIdsFilter(this);
sellableIds_AsyncTask.execute(sellableIdsUrl);
How can I return this list to the activity where I call it from and have the list able to use anywhere within the class? instead to the method sellableIdsFilter();
Create a listener interface
public interface OnSellableIdsFilterListener {
void onSellableIdsFilterSuccess(List<Integer> ids);
}
make the class you need to get the ids implement that interface
public class SomeClassThatNeedsTheIDs implements OnSellableIdsFilterListener {
void someVoid() {
//Pass the instance of this class to the constructor of the asynctask as a listener
new YourAsyncTask(this, this).execute();
}
#Override
public void onGetSellableIdsFilterSuccess(List<Integer> ids) {
// do whatever you want with the ids
}
}
Change your asynctask adding a listener to the constructor
public class YourAsyncTask extends AsyncTask< , , > {
Context context;
OnSellableIdsFilterListener listener;
public YourAsyncTask(Context context, OnSellableIdsFilterListener listener) {
this.listener = listener;
this.context = context;
}
// In onPostExecute check if listener!=null and invoke the method, passing the ids
public void onPostExecute() {
if(listener!=null) {
listener.onSellableIdsFilterSuccess(ids);
}
}
}
You can add a method which returns List 'items' in your 'sellableIds_AsyncTask' class. After calling asyncTasks execute , call get() to ensure async task has finished its task. Then call the getter function to get your List 'items'. Hope this helps.
In my MainActivity I have a method called getAPI that returns an OTBServiceWrapper. This is used to setup retrofit for calling to an API.
In my MainActivityTest file I am trying to stub out the new OTBService().getService() call that the getApi method is making so I can return a MockedOTBService which changes the client to a custom one that return json.
As is, the current implementation will it the MockedOTBService if I had to place a logger within MockedOTBService but also falls through and calls the real api, which is not want I want in a test.
I am trying to stub the Retrofit API calls using Mockito and return json. I cant seem to understand why the stub is being called yet is not stubbing the method in question.
Notes:
I am using ActivityInstrumentationTestCase2
I am only running one test
If I add a verify(mockedOTBService, atLeastOnce()).getService(); is says it was never called.
If I change the when...thenReturn to use a mMainActivity = spy(getActivity()) there is not change and the real API is called.
Logcat Output
Logger﹕ MockedOTBService was called // Mock is called
Logger﹕ Real OTBService was called // Real API is called
Logger﹕ MainActivity getAPI method class is "$Proxy1" // Mock is shown in MainActivity
Logger﹕ RealAPIResponse JSON Parsed ID: 266 // Real API response returned
Real Flow
MainActivity.onCreate() > OTBService.getService() > OTBServiceWrapper.createSearch(...)
Trying to Achieve within Tests
MainActivity.onCreate() > MockedOTBService.getService() > OTBServiceWrapper.createSearch(...)
MainActivity.java
public class MainActivity extends Activity {
private OTBServiceWrapper serviceWrapper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getApi().createSearch(...)
}
public OTBServiceWrapper getApi() {
return new OTBService().getService();
}
}
OTBService.java
public class OTBService {
public OTBServiceWrapper getService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(Constants.API_URL)
.build();
return restAdapter.create(OTBServiceWrapper.class);
}
}
OTBServiceWrapper.java
public interface OTBServiceWrapper {
#POST(Constants.API_SEARCHES_POST_URL)
void createSearch(#Body Request request, Callback<Request.Response> callback);
}
MainActivityTest.java
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private OTBService mMockedOTBService;
private MainActivity mMainActivity;
private View mSearchButton;
public MainActivityTest() { super(MainActivity.class); }
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(true);
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
mMockedOTBService = mock(OTBService.class);
when(mMockedOTBService.getService()).thenReturn(new MockedOTBService(getInstrumentation().getContext()).getService());
mMainActivity = getActivity();
mSearchButton = mMainActivity.findViewById(R.id.AbSearchButton);
mYourHolidayButton = mMainActivity.findViewById(R.id.AbYourHolidayButton);
}
public void testButtonActions() {
TouchUtils.clickView(this, mSearchButton);
...
}
}
MockedOTBService.java
public class MockedOTBService {
private Context context;
public MockedOTBService(Context context) { this.context = context; }
public OTBServiceWrapper getService() {
RestAdapter restAdapter;
restAdapter = new RestAdapter.Builder()
.setClient(new LocalJsonClient(context))
.setEndpoint(Constants.API_TEST_URL)
.build();
return restAdapter.create(OTBServiceWrapper.class);
}
}
LocalJsonClient.java
#SuppressLint("DefaultLocale")
public class LocalJsonClient implements Client { ... }
build.gradle
dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
}
Remove the need for mocking your request by allowing the Activity to set the service.
In your MainActivity create a class variable and a class setter for the service. It needs to be a at the class scope to prevent the OnCreate method being called before you have set the service to what you want it to be. Also create an instance getter which sets the service if you have not already.
In your test before you call getActivity() set the service to be your mock service. (Maybe think about moving this out to a support object).
MainActivity.java
public class MainActivity extends Activity {
private static OTBServiceWrapper serviceWrapper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getServiceWrapper.createSearch(...)
}
public OTBServiceWrapper getServiceWrapper() {
if (serviceWrapper == null) {
MainActivity.setServiceWrapper(new OTBService().getService());
}
return serviceWrapper;
}
public static void setServiceWrapper(OTBServiceWrapper serviceWrapper) {
MainActivity.serviceWrapper = serviceWrapper;
}
}
MainActivityTest.java
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity mMainActivity;
public MainActivityTest() { super(MainActivity.class); }
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(true);
MainActivity.setServiceWrapper(
new MockedOTBService(getInstrumentation().getContext()).getService()
);
mMainActivity = getActivity();
}
}