One of the feature of the Volley library for network operations in android is that it caches text and images into disk so by changing orientation the application does not have to download images and texts again. this piece of code uses this feature:
public class AppsActivity extends Activity {
static public Typeface font;
List<Application> appsList;
ArrayList<String> title = new ArrayList<String>();
ArrayList<String> description = new ArrayList<String>();
AppAdapter adapter;
GridView gv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_apps);
font =Typeface.createFromAsset(getAssets(), "fonts/BMitra.ttf");
appsList = new ArrayList<Application>();
gv = (GridView) findViewById(R.id.gridView1);
adapter = new AppAdapter(this, appsList);
gv.setAdapter(adapter);
Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get("http://appline.ir/index.php/android.feed?limitstart=");
if (entry != null)
{
fetch the data from cache
try {
String data = new String(entry.data, "UTF-8");
parseXmlString(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
else
{
StringRequest request = new StringRequest(Method.GET, "http://appline.ir/index.php/android.feed?limitstart=", new Listener<String>() {
#Override
public void onResponse(String res)
{
parseXmlString(res);
}
gv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long arg3) {
Intent intent = new Intent(AppsActivity.this,
AppDetailActivity.class);
Application app;
app=appsList.get(position);
intent.putExtra("title", app.getTitle());
intent.putExtra("imageUrl", app.getImageUrl());
intent.putExtra("description", description.get(position+1));
// intent.putExtra("title", app.getTitle());
startActivity(intent);
}
});
}
public void parseXmlString(String string)
{
XmlPullParserFactory parser;
XmlPullParser xml = null;
try
{
parser =XmlPullParserFactory.newInstance();
xml = parser.newPullParser();
StringReader reader = new StringReader(string);
xml.setInput(reader);
}
catch(Exception e)
{
System.err.println(e.getMessage());
}
int event;
String text = null;
try {
event = xml.getEventType();
while (event != XmlPullParser.END_DOCUMENT)
{
String name=xml.getName();
switch (event)
{
case XmlPullParser.START_TAG:
break;
case XmlPullParser.TEXT:
text = xml.getText();
break;
case XmlPullParser.END_TAG:
if(name.equals("title")){
title.add(text);
}
else if(name.equals("link")){
// link = text;
}
else if(name.equals("description")){
description.add(text);
}
else{
}
break;
}
event = xml.next();
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
System.out.println(description.size());
for(int i = 1; i < description.size(); i++)
{
Document doc = Jsoup.parse((description.get(i)));
Elements links = doc.select("p"); // a with href
Elements pngs = links.select("img[src$=.png]");
String image = pngs.attr("src");
appsList.add(new Application(Html.fromHtml(title.get(i)).toString(), image, font));
}
adapter.notifyDataSetChanged();
}
My question is that, when I debug the code(using System.out in if clause for cache), I figured out that we never get a hit on cache. what is wrong?
the server dose not allow you to cache those files, in order to confirm my answer you can do one of these things:
download this plug in (RESTClient) for mozilla and send your request and check the header file for cache control
set break point in headerValue = headers.get("Cache-Control"); at HttpHeaderParse class and see whats going on.
all of those solutions only valid if you set your requests to be cashable shouldCache();
and just in order to accept my answer faster, look at cache control :-)
Related
I created app that takes JSON with AsyncTask from server. When User click a button app starts new Activity and download data from server and show it as a items in ListView. The Problem is when I open new Activity it takes too long to load. When button is pressed app freeze on about one or two seconds and then show black screen for another 2/3 seconds. After that activity is displayed but it is very slow. It freeze every time user is scrolling or pressing button to display more options of custom adapter. Is there any way to make app more smooth? Json data that is downloaded is just simple JSONArray with JSONObjects that has 2 string values and one HTML format. This 3 values is display to user.
Part of Custom Adapter class
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
SuggestionList suggestionList = getItem(position);
int actualPosition = 0;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.sugestion_list, parent, false);
}
final Button suggestionsButton = (Button) convertView.findViewById(R.id.suggestionsMore);
final TextView suggestionNumber = (TextView) convertView.findViewById(R.id.sugestionNumber);
final TextView suggestionDescription = (TextView) convertView.findViewById(R.id.suggestionDescription);
final ImageView bio = convertView.findViewById(R.id.sugestionBio);
final ImageView block = convertView.findViewById(R.id.sugestionBlock);
final ImageView call = convertView.findViewById(R.id.sugestionCall);
...
final Animation slideUp = AnimationUtils.loadAnimation(getContext(), R.anim.slideup);
final Animation slideDown = AnimationUtils.loadAnimation(getContext(), R.anim.slidedown);
final Handler handler = new Handler();
suggestionsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (bioSuggestions.getVisibility() == View.GONE) {
bio.setVisibility(View.VISIBLE);
block.setVisibility(View.VISIBLE);
call.setVisibility(View.VISIBLE);
bioSuggestions.startAnimation(slideUp);
blockSuggestions.startAnimation(slideUp);
callSuggestions.startAnimation(slideUp);
} else if (bioSuggestions.getVisibility() == View.VISIBLE) {
bioSuggestions.startAnimation(slideDown);
blockSuggestions.startAnimation(slideDown);
callSuggestions.startAnimation(slideDown);
handler.postDelayed(new Runnable() {
#Override
public void run() {
bio.setVisibility(View.GONE);
block.setVisibility(View.GONE);
call.setVisibility(View.GONE);
}
}, 300);
}
}
});
if (actualPosition != position) {
if (bio.getVisibility() == View.VISIBLE) {
bio.setVisibility(View.GONE);
block.setVisibility(View.GONE);
call.setVisibility(View.GONE);
}
actualPosition = position;
}
JSONObject jsonValSuggestions = new getSugestions().sugestionsDetails(position, "suggestions");
try {
final String name = jsonValSuggestions.getString("client_name");
final String num = jsonValSuggestions.getString("client_number");
final String description = jsonValSuggestions.getString("client_description");
bio.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent suggestionsDetails = new Intent(view.getContext(), SuggestionsDetails.class);
suggestionsDetails.putExtra("client_number", num);
suggestionsDetails.putExtra("client_name", name);
suggestionsDetails.putExtra("client_description", description);
activity.startActivityForResult(suggestionsDetails, position);
}
});
block.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent suggestionBlock = new Intent(view.getContext(), BlockSuggestionsActivity.class);
activity.startActivity(suggestionBlock);
}
});
call.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent suggestionCall = new Intent(view.getContext(), CallSuggestionActivity.class);
suggestionCall.putExtra("client_number", num);
suggestionCall.putExtra("client_name", name);
activity.startActivity(suggestionCall);
}
});
} catch (Exception e) {
e.printStackTrace();
}
try {
if (suggestionList.suggestionName.equals("null") || suggestionList.suggestionName.equals("")) {
suggestionNumber.setText(suggestionList.suggestionNumber);
} else {
suggestionNumber.setText(suggestionList.suggestionName);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
suggestionDescription.setText(Html.fromHtml(suggestionList.suggestionDescription, Html.FROM_HTML_MODE_LEGACY));
} else {
suggestionDescription.setText(Html.fromHtml(suggestionList.suggestionDescription));
}
} catch (Exception e) {
Log.i("exception", e.getMessage());
}
return convertView;
}
Part of AsyncTask class
public static final String REQUEST_METHOD = "GET";
public static final int READ_TIMEOUT = 15000;
public static final int CONNECTION_TIMEOUT = 15000;
#Override
protected String doInBackground(String... params) {
String clientUrl = params[0];
String result;
String inputLine;
JSONObject obj;
String data;
String message;
try {
URL myUrl = new URL(clientUrl);
HttpURLConnection connection = (HttpURLConnection) myUrl.openConnection();
connection.setRequestMethod(REQUEST_METHOD);
connection.setReadTimeout(READ_TIMEOUT);
connection.setConnectTimeout(CONNECTION_TIMEOUT);
connection.connect();
InputStreamReader streamReader = new InputStreamReader(connection.getInputStream());
BufferedReader reader = new BufferedReader(streamReader);
StringBuilder stringBuilder = new StringBuilder();
while ((inputLine = reader.readLine()) != null) {
stringBuilder.append(inputLine);
}
reader.close();
streamReader.close();
result = stringBuilder.toString();
} catch (IOException e) {
e.printStackTrace();
result = null;
}
return result;
}
public String[] getSuggestionsList() {
String[] suggestionList = new String[5];
String result;
String status;
JSONObject listObj;
String suggestionsData;
JSONObject suggestionsDataObj;
JSONArray suggestionsDataArr;
String ClientsSugestionsUrl = "https://example.com/token=" + authToken;
getApiClientSugestions getSugestionsFromApi = new getApiClientSugestions();
try {
result = getSugestionsFromApi.execute(ClientsSugestionsUrl).get();
try {
listObj = new JSONObject(result);
status = listObj.getString("result");
suggestionsData = listObj.getString("suggestions");
suggestionsDataArr = new JSONArray(suggestionsData);
} catch (Exception e) {
e.printStackTrace();
suggestionsDataArr = null;
status = null;
}
suggestionList[3] = status;
suggestionList[4] = suggestionsDataArr.toString();
} catch (Exception e) {
e.printStackTrace();
}
return suggestionList;
}
Activity
public class CallsSuggestionsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calls_suggestions);
Slidr.attach(this);
getSupportActionBar().setTitle("Skontaktuj siÄ™");
}
#Override
protected void onResume() {
super.onResume();
CallsSuggestionList();
}
public void CallsSuggestionList() {
final ListView suggestionList = findViewById(R.id.sugestionList);
final ArrayList<SuggestionList> suggestionArray = new ArrayList<SuggestionList>();
SuggestionListAdapter suggestionListAdapter = new SuggestionListAdapter(getContext(), suggestionArray, this);
String[] suggestionListArray = new getSugestions().getSuggestionsList();
String suggStat = suggestionListArray[3];
String arrayList = suggestionListArray[4];
String clientName;
String clientNumber;
String clientDescription;
try {
JSONArray jsonArray = new JSONArray(arrayList);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject explrObject = jsonArray.getJSONObject(i);
clientName = explrObject.getString("client_name");
clientNumber = explrObject.getString("client_number");
clientDescription = explrObject.getString("client_description");
if (suggStat.equals("true")) {
SuggestionList suggestionList1 = new SuggestionList(clientName, clientDescription, clientNumber);
suggestionListAdapter.addAll(suggestionList1);
suggestionListAdapter.notifyDataSetChanged();
suggestionList.setAdapter(suggestionListAdapter);
}
}
} catch (Exception e) {
Log.i("exception", e.getMessage());
e.printStackTrace();
clientName = null;
clientDescription = null;
clientNumber = null;
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
SuggestionList
public class SuggestionList {
public String suggestionNumber;
public String suggestionDescription;
public String suggestionCallType;
public String suggestionName;
public SuggestionList(
// String suggestionCallType,
String suggestionName, String suggestionDescription, String suggestionNumber) {
this.suggestionNumber = suggestionNumber;
// this.suggestionCallType = suggestionCallType;
this.suggestionName = suggestionName;
this.suggestionDescription = suggestionDescription;
}
}
Adapter are custom with custom view displayed to user. I use similar custom adapter to show content from sqlite that is on phone and there app isn't so slow. But when I open this activity it slow down dramatically. Also I noticed when I press back button it take very long to back to previous screen.
The problem is in the getSuggestionsList function. in this function, you are calling getSugestionsFromApi.execute(ClientsSugestionsUrl).get(); which make your code sync again. I mean your code is waiting this code to be executed.
One way (not right way, but easy way): you can call new getSugestions().getSuggestionsList(); in a new thread.
Second way, call getSugestionsFromApi.execute(ClientsSugestionsUrl) without get() function. But to get result of the code, you need to give an interface.
To get right usage: https://xelsoft.wordpress.com/2014/11/28/asynctask-implementation-using-callback-interface/
How to achieve this?
Okay I have this process
On Index Activity, when I click the side nav Profile, it will call Fragment (Profile) thru Fragment Transaction
if (id == R.id.nav_profile){
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.setCustomAnimations(R.anim.enter,R.anim.exit,R.anim.enter,R.anim.exit);
transaction.replace(R.id.flContent, new Profile(), "ProfileFragment");
transaction.addToBackStack(null);
viewPager.getAdapter().notifyDataSetChanged();
transaction.commit();
}
Now I'm on the Profile Fragment:
When I click "Apply for Developer" button, it will call DevRegistration activity by Intent
case 1:
btnBeDeveloper.setText("Apply for Developer");
btnBeDeveloper.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent index = new Intent(getActivity(), DevRegistration.class);
startActivity(index);
}
});
break;
Step 3:
On DevRegistration after I click Register, it will display a dialog fragment
FragmentManager fm = getSupportFragmentManager();
df_SuccessDevRegistration dialogFragment = new df_SuccessDevRegistration();
dialogFragment.show(fm, "Dev Register.");
Now this is my Dialog Fragment code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
View rootView = inflater.inflate(R.layout.fragment_success_developer_registration, container, false);
btnDevGoProfile = (Button) rootView.findViewById(R.id.btnDevGoProfile);
btnDevGoProfile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
getActivity().finish();
}
});
return rootView;
}
Now my problem is, when I press the button on the dialog fragment, it should refresh my profile fragment. how to achieve that? I tried detach attach but my app just crashed and for null exception.
public class Profile extends Fragment {
TextView txvStudID, txvStudUsername, txvStudEmail, txvStudBday,txvProfileName;
CollapsingToolbarLayout ctlName;
ImageView imvProfilePic;
Button btnBeDeveloper;
View rootView;
String picURL;
FragmentTransaction ft; Fragment currentFragment;
/** Recycler View**/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rootView = inflater.inflate(R.layout.activity_profile, container, false);
Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.profile_toolbar);
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view) {
NavigationView navigationView = (NavigationView) getActivity().findViewById(R.id.nav_view);
getFragmentManager().popBackStack();
navigationView.getMenu().getItem(0).setChecked(true);
}
});
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
((AppCompatActivity)getActivity()).getSupportActionBar().setHomeButtonEnabled(true);
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayShowTitleEnabled(true);
txvStudID = (TextView) rootView.findViewById(R.id.txvStudID);
txvStudUsername = (TextView) rootView.findViewById(R.id.txvStudUsername);
txvStudEmail = (TextView)rootView.findViewById(R.id.txvStudEmail);
txvStudBday = (TextView) rootView.findViewById(R.id.txvStudBday);
txvProfileName = (TextView) rootView.findViewById(R.id.txvProfileName);
ctlName = (CollapsingToolbarLayout) rootView.findViewById(R.id.ctlName);
imvProfilePic = (ImageView) rootView.findViewById(R.id.imvProfilePic);
btnBeDeveloper = (Button) rootView.findViewById(R.id.btnBeDeveloper);
checkLevelOfAccess();
/** get user's data **/
getCurrentUserData(DataClass.currentEnteredUsername);
return rootView;
}
public void checkLevelOfAccess()
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String acctID = preferences.getString("acctID", "DEFAULT");
switch (checkLevelOfAccess(acctID)) {
// if the level of access is developer
case 1:
btnBeDeveloper.setText("Developer Console");
btnBeDeveloper.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent index = new Intent(getActivity(), UploadApp.class);
startActivity(index);
}
});
break;
// else if the level of access is not a developer
case 0:
switch (checkIfRegistered(acctID))
{
//already apply
case 0:
btnBeDeveloper.setText("Cancel Developer Request");
break;
case 1:
btnBeDeveloper.setText("Apply for Developer");
btnBeDeveloper.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent index = new Intent(getActivity(), DevRegistration.class);
startActivityForResult(index, 10001);
}
});
break;
}
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if ((requestCode == 10001) && (resultCode == Activity.RESULT_OK))
{
System.out.println("IM HERE NA!!!");
checkLevelOfAccess();
}
}
public void getCurrentUserData(String currentUser) {
try {
String accountURL = DataClass.localAddress + "android_php/account.php";
URL url = new URL(accountURL);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
String finalJson = buffer.toString();
JSONObject parentObject = new JSONObject(finalJson);
JSONArray parentArray = parentObject.getJSONArray("users");
for (int x = 0; x < parentArray.length(); x++) {
JSONObject finalObject = parentArray.getJSONObject(x);
if (finalObject.getString("username").equalsIgnoreCase(currentUser) || finalObject.getString("studID").equalsIgnoreCase(currentUser)
|| finalObject.getString("email").equalsIgnoreCase(currentUser)) {
txvStudID.setText(finalObject.getString("studID"));
txvStudUsername.setText(finalObject.getString("username"));
txvStudEmail.setText(finalObject.getString("email"));
txvStudBday.setText(convertDate(finalObject.getString("bday")));
ctlName.setTitle(finalObject.getString("name"));
txvProfileName.setText(finalObject.getString("name"));
//set Profile Picture
picURL = DataClass.localAddress + finalObject.getString("pic");
LoadImageFromURL loadImage = new LoadImageFromURL();
loadImage.execute();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String convertDate(String date) throws ParseException {
String strCurrentDate = date;
DateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd");
DateFormat outputFormat = new SimpleDateFormat("dd MMM yyyy");
Date parseDate = inputFormat.parse(strCurrentDate);
String converted = outputFormat.format(parseDate);
return converted;
}
public class LoadImageFromURL extends AsyncTask<String, Void, Bitmap>{
#Override
protected Bitmap doInBackground(String... params) {
// TODO Auto-generated method stub
try {
InputStream is = new URL(picURL).openConnection().getInputStream();
Bitmap bitMap = BitmapFactory.decodeStream(is);
GetCircleBitmap gcb = new GetCircleBitmap();
Bitmap b = gcb.GetCircleBitmap(bitMap);
return b;
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
imvProfilePic.setImageBitmap(result);
}
}
public int checkLevelOfAccess(String data)
{
try {
String accountURL = DataClass.localAddress + "android_php/account.php";
URL url = new URL(accountURL);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
String finalJson = buffer.toString();
JSONObject parentObject = new JSONObject(finalJson);
JSONArray parentArray = parentObject.getJSONArray("users");
for (int x = 0; x < parentArray.length(); x++) {
JSONObject finalObject = parentArray.getJSONObject(x);
// true
if (finalObject.getString("acctid").equalsIgnoreCase(data) && finalObject.getString("acctlofa").equals("1"))
{
return 1;
}
else if (finalObject.getString("acctid").equalsIgnoreCase(data) && finalObject.getString("acctlofa").equals("0"))
{
return 0;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return 2;
}
/** Check if the user already applied to become a developer **/
public int checkIfRegistered(String data) {
try {
String accountURL = DataClass.localAddress + "android_php/developer_account.php";
URL url = new URL(accountURL);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
String finalJson = buffer.toString();
JSONObject parentObject = new JSONObject(finalJson);
JSONArray parentArray = parentObject.getJSONArray("users");
for (int x = 0; x < parentArray.length(); x++) {
JSONObject finalObject = parentArray.getJSONObject(x);
// pending application
if (finalObject.getString("acctID").equalsIgnoreCase(data) && finalObject.getString("devStatus").equals("0"))
{
return 0;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}
}
I'dd suggest you using intents between activity call, and StartForRsults().
Then when your activity resume using public void onActivityResult(int requestCode, int resultCode, Intent intent) you'll be able to check if the user correctly registered (and not cancelled registration) through a boolead in the intent.
Then, set a global variable "refreshProfile", and make it true when your activity resume with the correct intent saying user registered. Override public void onPostResume(), use a if statement to check wether or not you need to refresh the profile. If yes, do so then make the variable false. if not, do nothing.
Doing so will prevent you from updating your profileFragment when activity is resuming wich can cause some crash sometimes ("IllegalStateException: Can not perform this action after onSaveInstanceState") because you are updating the UI at the wrong moment.
Other option consists into changing the dialogFragment, setting a custom function to refresh your activity.
build.setPositiveButton(positiveButton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
/*refresh profile here
with instruction like getActivity().refresh();
where refresh is your activity public function to refresh profile.
*/
}
});
However, i dislike that way because your profile will wait user to confirm before being updated. I find it more clearer and elegant to have profile updated THEN prevent the user everything went fine. But personnal opinion here.
In your fragment that you need to refresh create and register a broadcast receiver.
When you want to refresh the fragment (in your case when clicking the button on the dialog fragment) send a broadcast which will be caught by your broadcast receiver on the profile fragment.
This will also make sure that the solution is not too tightly coupled.
You need to return the result back to the activity which contains the profile data. This can be done in several ways. In this case, I assume you want the profile to remain changed permanently. For example if the user quits the app and starts it again a week later, the changes should persist to the profile screen. This suggests that you need the data to be stored permanently. For small amounts of data, such as a boolean flag or a String, you can use SharedPreferences. For more complex data, use a SQLite database or a flat file. Now the dialog just saves the data to the chosen medium and the profile fragment loads the data in onCreate().
This is my code to save the gridview scroll state. But it is not retrieved in the restore instance. Only this step is required for my project to pass.
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance){
rootView = inflater.inflate(R.layout.fragment_movies, null);
if (savedInstance != null) {
MovieTask.mProgressDialog.onRestoreInstanceState(savedInstance);
gridView.smoothScrollToPosition(savedInstance.getInt("scroll"));
}
setHasOptionsMenu(true);
return rootView;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
MovieTask.mProgressDialog.onSaveInstanceState();
savedInstanceState.putInt("scroll",gridView.getFirstVisiblePosition());
super.onSaveInstanceState(savedInstanceState);
}
Here is the AsyncTask code. Where I am initializing the gridview.
#Override
protected void onPreExecute(){
mProgressDialog = new ProgressDialog(mContext);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String categoryType = preferences.getString(mContext.getString(R.string.pref_key_category) ,mContext.getString(R.string.pref_category_default_value));
if(categoryType.equals(Constants.TOP_RATED_KEY)){
Category = Constants.TOP_RATED_KEY;
mProgressDialog.setTitle("Top Rated Movies");
} else if (categoryType.equals(Constants.POPULARITY_KEY)){
Category = Constants.POPULARITY_KEY;
mProgressDialog.setTitle("Popular Movies");
}
mProgressDialog.setMessage("Loading...");
mProgressDialog.setIndeterminate(false);
mProgressDialog.show();
}
#Override
protected String[] doInBackground(String... strings) {
try {
URL url = new URL(Constants.MOVIE_URL + Category + Constants.API_KEY);
Log.d(LOG_TAG, String.valueOf(url));
// Create the request to OpenWeatherMap, and open the connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null) {
// Nothing to do.
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
// But it does make debugging a *lot* easier if you print out the completed
// buffer for debugging.
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
// Stream was empty. No point in parsing.
return null;
}
movieJsonStr = buffer.toString();
} catch (IOException e) {
Log.e("PlaceholderFragment", "Error ", e);
// If the code didn't successfully get the weather data, there's no point in attemping
// to parse it.
return null;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("PlaceholderFragment", "Error closing stream", e);
}
}
}
try {
return getMovieDataFromJson(movieJsonStr);
} catch (JSONException e) {
Log.d(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String[] result){
if(result != null) {
mProgressDialog.dismiss();
if(NetworkUtility.isOnline(mContext)){
List<Model> favoriteMovie = Model.listAll(Model.class);
MovieAdapter movieAdapter = new MovieAdapter(mContext, favoriteMovie, new MovieAdapter.Callback() {
#Override
public void onItemSelected(String title, String poster, String plot, double ratings, String release, long id) {
if (MainActivity.mTwoPane) {
((FragmentActivity)mContext).getSupportFragmentManager().beginTransaction()
.replace(R.id.details_fragment_container, DetailsFragment.newInstance(title, poster, plot, ratings, release, id))
.commit();
} else {
Intent intent = new Intent(mContext, DetailsActivity.class);
intent.putExtra("title", title);
intent.putExtra("poster", poster);
intent.putExtra("plot",plot);
intent.putExtra("ratings", ratings);
intent.putExtra("release", release);
intent.putExtra("id", id);
mContext.startActivity(intent);
}
}
});
MoviesFragment.gridView = (GridView) MoviesFragment.rootView.findViewById(R.id.grid_view);
MoviesFragment.gridView.setAdapter(movieAdapter);
int index = MoviesFragment.gridView.getFirstVisiblePosition();
MoviesFragment.gridView.smoothScrollToPosition(index);
} else {
Toast.makeText(mContext,"Please Enable Internet Services",Toast.LENGTH_SHORT).show();
}
}
}
Do you use to shared preference Otherway try it
gridView.setSelection(savedInstance.getInt("scroll"))
Do you know when onSaveInstanceState and onRestoreInstanceStatewill be called. If you manually close the activity, for example hit back, the savedInstance will always be null.
I think you are looking for sharedPreference to store the scroll position
At the following point during the execution of onCreateView(), it looks like the AsyncTask has been started (at least you're accessing MovieTask.mProgressDialog and nothing crashes, so I think onPreExecute() has finished successfully).
MovieTask.mProgressDialog.onRestoreInstanceState(savedInstance);
gridView.smoothScrollToPosition(savedInstance.getInt("scroll"));
If onPostExecute() runs after these lines are executed, then the following statements will simply set the scroll position to the beginning of the content:
MoviesFragment.gridView.setAdapter(movieAdapter);
int index = MoviesFragment.gridView.getFirstVisiblePosition();
MoviesFragment.gridView.smoothScrollToPosition(index);
This happens because movieAdapteris created from scratch and the GridView is informed that from now on the content to display will be provided by this adapter. So the usual thing happens: getCount() is called, the GridView is filled from the beginning and the scroll position is set to the beginning as well.
To get the desired scroll position, you can introduce a variable
int lastScrollPosition = -1;
in MovieTask. Set the value to the desired position in onCreateView() if the savedInstanceState != null
int index = savedInstance.getInt("scroll", -1);
// case 1: AsyncTask still busy
MovieTask.lastScrollPosition = index;
// case 2: AsyncTask already finished
if (index >= 0)
{
// seems from the source of AbsListView = parent of GridView
// that there will be no crash if index is too big
MoviesFragment.gridView.smoothScrollToPosition(index);
}
and read it in onPostExecute():
if (lastScrollPosition >= 0 and lastScrollPosition < movieAdapter.getCount())
{
MoviesFragment.gridView.smoothScrollToPosition(lastScrollPosition);
}
This way, the scroll position will be used if possible.
I am writing an app to pull movie data from the MovieDB api and display the information in a GridView.
When the app first loads I would expect the view to populate initially, but it does not. I have a sort option in the menu bar and when the sort option is set the first time the GridView is populated by movies in order of popularity, as it should initially, but regardless of what sort criteria is actually selected.
I have used the logs to determine that the correct data is being retrieved from the API and being processed properly, so I have to assume that the adapter is not updating the view properly.
Why isn't the view showing initially or updating as it should?
FilmFragment.java:
public class FilmFragment extends Fragment {
private ArrayList<FilmParcelable> filmParcels = new ArrayList<FilmParcelable>();
private ImageAdaptor mFilmAdaptor;
protected String[] sortOptions = {
"popularity.desc",
"vote_average.desc"
};
protected String sortBy = sortOptions[0];
private final String LOG_TAG = FilmFragment.class.getSimpleName();
public FilmFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if (savedInstanceState == null || !savedInstanceState.containsKey("films")){
updateFilms();
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
} else {
filmParcels = savedInstanceState.getParcelableArrayList("films");
mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
}
// allow fragment to handle menu events
setHasOptionsMenu(true);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.filmfragment, menu);
}
public boolean onOptionsItemSelected(MenuItem item){
//Handle action bar item clicks. The action bar will
//automatically handle clicks on the Home/Up button, so long
//as you specify a parent activity in AndroidManifest.xml
int id = item.getItemId();
if (id == R.id.action_sort){
showSortDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onSaveInstanceState(Bundle outState){
outState.putParcelableArrayList("films", filmParcels);
super.onSaveInstanceState(outState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// Find GridView to populate with poster images
GridView gridView = (GridView) rootView.findViewById(R.id.gridView);
// Set the adaptor of the GridView to my ImageAdaptor
gridView.setAdapter(mFilmAdaptor);
updateAdaptor();
return rootView;
}
// Update movie data in case there is a change in the "sort by" option
// Or the fragment is started with no saved data
public void updateFilms(){
new FetchFilmTask().execute();
}
public void updateAdaptor(){
mFilmAdaptor.clear();
mFilmAdaptor.addAll(filmParcels);
mFilmAdaptor.notifyDataSetChanged();
}
// Show dialog sort pop up
public void showSortDialog(){
DialogFragment dialog = new SortDialog();
dialog.setTargetFragment(this, 0);
dialog.show(getActivity().getSupportFragmentManager(), "SortDialog");
}
// If a fragment or activity called by this fragment returns to this fragment,
// Get the information returned via the intent
public void onActivityResult(int requestCode, int resultCode, Intent data){
if (requestCode == 0){
int mSelected = data.getIntExtra("Selected Option", -1);
if (mSelected != -1){
sortBy = sortOptions[mSelected];
updateFilms();
updateAdaptor();
}
}
}
// Class to get JSON data from The Movie Database API
public class FetchFilmTask extends AsyncTask<Void, Void, FilmParcelable[]> {
private final String LOG_TAG = FetchFilmTask.class.getSimpleName();
private final String MOVIE_DB_API_KEY = "e1968ef8ba074d7d5bf07a59de8b2310";
protected FilmParcelable[] doInBackground(Void... params){
// These two need to be declared outside the try/catch
// so that they can be closed in the finally block.
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
// Will contain raw JSON response as a string
String movieDBStr = null;
try {
// Construct URL for Movie DB query
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority("api.themoviedb.org")
.appendPath("3")
.appendPath("discover")
.appendPath("movie")
.appendQueryParameter("api_key", MOVIE_DB_API_KEY)
.appendQueryParameter("sort_by", sortBy);
String myUrl = builder.build().toString();
Log.d(LOG_TAG, myUrl);
URL url = new URL(myUrl);
// Create the request to The Movie DB, and open the connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null){
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
// But it does make debugging a *lot* easier if you print out the completed
// buffer for debugging.
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
return null;
}
movieDBStr = buffer.toString();
} catch (IOException e){
Log.e(LOG_TAG, "Error: ", e);
return null;
} finally {
if (urlConnection != null){
urlConnection.disconnect();
}
if (reader != null){
try{
reader.close();
} catch (final IOException e){
Log.e(LOG_TAG, "Error closing stream", e);
}
}
}
try {
return getFilmDataFromJson(movieDBStr);
} catch (JSONException e){
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
return null;
}
/**
* Take the String representing the complete forecast in JSON Format and
* pull out the data we need to construct the Strings needed for the wireframes.
*
* Fortunately parsing is easy: constructor takes the JSON string and converts it
* into an Object hierarchy for us.
*/
private FilmParcelable[] getFilmDataFromJson(String movieDBStr)
throws JSONException {
// JSON objects that need to be extracted
final String MDB_RESULTS = "results";
final String MDB_ID = "id";
final String MDB_SYNOPSIS = "overview";
final String MDB_RELEASE = "release_date";
final String MDB_POSTER = "poster_path";
final String MDB_TITLE = "title";
final String MDB_RATING = "vote_average";
JSONObject filmJson = new JSONObject(movieDBStr);
JSONArray filmArray = filmJson.getJSONArray(MDB_RESULTS);
FilmParcelable[] resultFilms = new FilmParcelable[filmArray.length()];
for (int i = 0; i < filmArray.length(); i++){
// Data needed by the FilmParcelable
int id;
String title;
String releaseDate;
String posterUrl;
Double voteAverage;
String plotSynopsis;
JSONObject film = filmArray.getJSONObject(i);
id = film.getInt(MDB_ID);
plotSynopsis = film.getString(MDB_SYNOPSIS);
releaseDate = film.getString(MDB_RELEASE);
posterUrl = "http://image.tmdb.org/t/p/w300" + film.getString(MDB_POSTER);
title = film.getString(MDB_TITLE);
voteAverage = film.getDouble(MDB_RATING);
Log.d(LOG_TAG, title);
Log.d(LOG_TAG, posterUrl);
resultFilms[i] = new FilmParcelable(id, title, releaseDate, posterUrl, voteAverage, plotSynopsis);
}
return resultFilms;
}
#Override
protected void onPostExecute(FilmParcelable[] result){
if (result != null){
filmParcels = new ArrayList<>(Arrays.asList(result));
}
}
}
}
ImageAdaptor.java:
public class ImageAdaptor extends ArrayAdapter<FilmParcelable> {
public ImageAdaptor(Activity context, ArrayList<FilmParcelable> filmParcels){
super(context, 0, filmParcels);
}
public View getView(int position, View convertView, ViewGroup parent){
Context context= getContext();
View gridView;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
String mUrl = getItem(position).getUrl();
if (convertView == null) {
gridView = inflater.inflate(R.layout.gridview_film_layout, parent, false);
// Find the image view from the gridview_film_layout
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
// Set the image view to contain image located at mUrl
Picasso.with(getContext()).load(mUrl).into(posterView);
} else {
gridView = convertView;
}
return gridView;
}
}
accordingly to the code you posted you are returning over and over the same cell of your GridView. You should have those two lines
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
out of the if/else guard:
if (convertView == null) {
// inflate
} else {
// gridView = convertView;
}
ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);
return gridView;
I was attempting to update the adapter after populating the arraylist used for the adapter. However the arraylist was populated and updated in the background so the code:
updateFilms();
updateAdaptor();
was causing the adaptor to update before the data had completed loading in the background.
After fixing that Blackbelt's solution was correct.
I'm having a this error Only the original thread that created a view hierarchy can touch its views.
But i don't know how to handle the ui in multithread, like list view and i don't know where to put it.
// Download JSON in Background
public class DownloadJSONFileAsync extends AsyncTask<String, Void, Void> {
protected void onPreExecute() {
super.onPreExecute();
showDialog(DIALOG_DOWNLOAD_JSON_PROGRESS);
}
#Override
protected Void doInBackground(String... params) {
deviceId = generateDeviceId();
elementsList.add(new BasicNameValuePair("DeviceID", deviceId));
JSONObject json = jsonParser.makeHttpRequest(
url_get_reports, "POST", elementsList);
Log.d("Create Response", json.toString());
// check for success tag
try {
int success = json.getInt(TAG_SUCCESS_REPORT);
// successfully created
// getting JSON string from URL
try {
if (success == 1) {
reports = json.getJSONArray(TAG_Report);
report_data = new Report[reports.length()];
// looping through All Products
for (int i = reports.length()-1; i >= 0; i--) {
JSONObject c = reports.getJSONObject(i);
reportID = c.getString("reportID");
Drawable drawable = LoadImageFromWeb(url+c.getString(TAG_IMAGE));
String state = "";
if(c.getString("state").equals("1")){
state = "Pinding";
}else if(c.getString("state").equals("2")){
state = "Inprogress";
}else{
state = "Completed";
}
if(c.getString(TAG_TITLE).equals("")){
report_data[i] = new Report(reportID,"Report "+(i+1),c.getString(TAG_TIME),drawable,url+c.getString(TAG_IMAGE),state,c.getString("coordination")) ;
}else{
report_data[i] = new Report(reportID,c.getString(TAG_TITLE),c.getString(TAG_TIME),drawable,url+c.getString(TAG_IMAGE),state,c.getString("coordination"));
}
if(json.getInt("successReplies")==1){
replies = json.getJSONArray(TAG_REPLIES);
ArrayList<String> arr = new ArrayList<String>();
for(int j=0;j<replies.length();j++){
JSONObject c2 = replies.getJSONObject(j);
if(reportID.equals(c2.getString("ReportID")))
{
arr.add(c2.getString("RepliesContent")+"\n"+c2.getString("RepliesTime"));
}
}
report_data[i].setRepliesArray(arr);
}
}
list = (ListView)findViewById(R.id.list);
ReportAdapter adapter = new ReportAdapter(Display_Reports.this,report_data);
list.setAdapter(adapter);
// Click event for single list row
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(
AdapterView<?> arg0, View arg1,
int position, long id) {
Intent intent = new Intent(Display_Reports.this, Display_Report_Details.class);
intent.putExtra("ID", report_data[position].ID);
intent.putExtra("Name", report_data[position].Name);
intent.putExtra("State", report_data[position].State);
intent.putExtra("Time", report_data[position].Time);
intent.putExtra("ImageUrl", report_data[position].ImageUrl);
intent.putExtra("Coordination", report_data[position].Coordination);
intent.putStringArrayListExtra("RepliesContent", report_data[position].getRepliesArray());
startActivity(intent);
}
});
} else {
// no Entities found
Toast.makeText(Display_Reports.this, "No reports found",
Toast.LENGTH_LONG).show();
}
} catch (JSONException e) {
e.printStackTrace();
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void unused) {
dismissDialog(DIALOG_DOWNLOAD_JSON_PROGRESS);
removeDialog(DIALOG_DOWNLOAD_JSON_PROGRESS);
}
}
You cannot modify the UI from a background thread. Whether you use AsyncTask, or a Handler, or runOnUiThread(), or post(), to have work done on the main application thread is up to you.
You may wish to review the documentation on threads and some of these techniques.