1

I don't have much experience with building well-designed object oriented systems, and this time I improvised, which lead to the system not working and not giving me any errors.

Basically in my android app, I have a user profile activity that calls a class that queries the remote database using the user ID, and returns values for user avatar and user name.

Until the class was nested inside the profile activity class it was alright, but I decided to move it out of there and change some other stuff and now when I go to My profile I do not see my avatar and I do not see my user name.

Here is the GetUserData class:

public class GetUserData extends Activity {
    private String currentlyLoggedInUserString;
    SharedPreferences sharedPrefs;
    Editor editor;
    int currentlyLoggedInUser;
    private JSONParser jsonParser = new JSONParser();
    private Configurationz configurationz = new Configurationz();
    private ToastMaker toastMaker = new ToastMaker();
    private static final String TAG_SUCCESS = "success";
    private static final String TAG_USER_AVATAR = "user_avatar";
    private static final String TAG_USER_NAME = "user_name";
    private static final String TAG_USER_EMAIL = "user_email";
    private static final String TAG_USER_SEX = "user_sex";
    private static final String TAG_USER_DATE_REGISTERED = "user_date_registered";
    private static final String TAG_USER_LAST_SEEN = "user_last_seen";
    private static final String TAG_USER_PASSWORD = "user_password";
    private static final String APP_SHARED_PREFS = "asdasd_preferences";
    private String userName;
    private String userEmail;
    private String userSex;
    private String userPassword;
    private String userAvatar;
    private String userDateRegistered;
    private String userLastSeen;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    public String getUserAvatar() {
        return userAvatar;
    }

    public void setUserAvatar(String userAvatar) {
        this.userAvatar = userAvatar;
    }

    public String getUserDateRegistered() {
        return userDateRegistered;
    }

    public void setUserDateRegistered(String userDateRegistered) {
        this.userDateRegistered = userDateRegistered;
    }

    public String getUserLastSeen() {
        return userLastSeen;
    }

    public void setUserLastSeen(String userLastSeen) {
        this.userLastSeen = userLastSeen;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sharedPrefs = getApplicationContext().getSharedPreferences(APP_SHARED_PREFS, Context.MODE_PRIVATE);
        new GetUserDataGetter().execute();
    }

    public class GetUserDataGetter extends AsyncTask<String, String, String> {

        @Override
        protected String doInBackground(String... params) {
            int success;
            try {

                List<NameValuePair> parameters = new ArrayList<NameValuePair>();
                // fix these shitty variables.
                currentlyLoggedInUser = sharedPrefs.getInt("currentLoggedInUserId", 0);
                currentlyLoggedInUserString = Integer.toString(currentlyLoggedInUser);
                parameters.add(new BasicNameValuePair("user_id", currentlyLoggedInUserString));

                final JSONObject json = jsonParser.makeHttpRequest(configurationz.URL_PHP_GET_USER_DATA, "POST", parameters); 
                success = json.getInt(TAG_SUCCESS);
                if (success == 1) {
                    // user data found
                    setUserLastSeen(json.getString(TAG_USER_LAST_SEEN));
                    setUserDateRegistered(json.getString(TAG_USER_DATE_REGISTERED));
                    setUserAvatar(json.getString(TAG_USER_AVATAR));
                    setUserSex(json.getString(TAG_USER_SEX));
                    setUserPassword(json.getString(TAG_USER_PASSWORD));
                    setUserEmail(json.getString(TAG_USER_EMAIL));
                    setUserName(json.getString(TAG_USER_NAME));

                    //return json.getString(TAG_USER_AVATAR);
                    return null;
                } else if (success == 2) {
                    //toast about not being able to connect to db;
                    runOnUiThread(new Runnable() {
                        public void run() {

                            //this might cause some SHIT!!!!!!!!!!!! TEST IT!!!
                            toastMaker.toast(getBaseContext(), configurationz.ERROR_MESSAGES_SIGNUP_DEVICE_UNABLE_TO_TAKE_PHOTOS, configurationz, Toast.LENGTH_LONG);

                        }
                        });


                    setUserLastSeen("");
                    setUserDateRegistered("");
                    setUserAvatar("");
                    setUserSex("");
                    setUserPassword("");
                    setUserEmail("");
                    setUserName("");
                    return null;
                } else {

                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return null;
        }

    }
}

and here is the MyProfile class:

public class MyProfile extends ActionBarAndSlidingMenu {
    private TableRow myProfileActionButtonsHolder;
    private TextView tvUserName;
    private ImageButton iUserAvatar;
    private Bitmap iUserAvatarBitmap;
    private String avatarPath;
    private String userName;
    private static final String APP_SHARED_PREFS = "asdasd_preferences";
    SharedPreferences sharedPrefs;
    Editor editor;
    int currentlyLoggedInUser;
    boolean userLoggedInState = false;
    private GetUserData getUserData = new GetUserData();

    public MyProfile() {
        super(R.string.app_name);
    }

    // do a check here whether this is the user themselves or some other user

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sharedPrefs = getApplicationContext().getSharedPreferences(APP_SHARED_PREFS, Context.MODE_PRIVATE);
        setContentView(R.layout.user_profile);

        // check whether user is logged in, otherwise redirect them to
        // login/signup page
        userLoggedInState = sharedPrefs.getBoolean("userLoggedInState", false);
        if (!userLoggedInState) {
            // start intent to get them out of here.
            // Research whether this step is necessary at all
        }

        // define the view components
        myProfileActionButtonsHolder = (TableRow) findViewById(R.id.userProfileActionButtonsHolder);

        // set avatar image
        iUserAvatar = (ImageButton) findViewById(R.id.iUserAvatar);
        avatarPath = getUserData.getUserAvatar();
        if (avatarPath != "") {
            iUserAvatarBitmap = BitmapFactory.decodeFile(avatarPath);
            iUserAvatar.setImageBitmap(iUserAvatarBitmap);
        } else {
            iUserAvatar.setImageResource(R.drawable.avatar_default_male);
        }

        //set user display name
        userName = getUserData.getUserName();
        tvUserName = (TextView) findViewById(R.id.tvUserName);
        tvUserName.setText(userName);


        // create action buttons fragment with "edit" and "settings" buttons
        getSupportFragmentManager().beginTransaction().replace(R.id.userProfileActionButtonsHolder, new MyProfileActionButtonsFragment()).commit();

    }
}
WhatsThePoint
  • 3,395
  • 8
  • 31
  • 53
Kaloyan Roussev
  • 14,515
  • 21
  • 98
  • 180
  • In the MyProfile Class getUserData is just created with no assigned data (from what I can tell with classes provided), it would explain why username and avatar are null. What calls onCreate for getUserData? – AbstractChaos Nov 01 '13 at 13:45
  • I dont know the answer to your question. How can I fix this? – Kaloyan Roussev Nov 01 '13 at 13:51

2 Answers2

3

First, you need to read up on programming in general and proper coding guidelines in particular, as this is a bit of a chaos. As soon as your project becomes more complex, this gets unreadable and undebuggable. Second, you should read up on how Android works.

Here's your problem in a nutshell:

An Activity is not just Android's own version of a class and you can't use it as such. An Activity represents a screen that is displayed to the user. No screen to display? No Activity. Thus, your getUserData Activity should be a regular class and not extend activity.

Now, in MyProfile you just declare a member variable with

private GetUserData getUserData = new GetUserData();

This does nothing and it certainly never runs that class' onCreate. Thus, your task is never executed and all your fields return null.

Here's what to do in a nutshell:

  1. Create a class UserDetails that has a constructor that takes the username, etc. plus the getters necessary to get these details. Add nothing else. This is what we call Java's version of a value object.

    public class UserDetails {
        private final String mUsername;
    
        public UserDetails(String username) {
             mUsername = username;
        }
    
        public String getUsername() {
            return mUsername;
        }
    }
    
  2. Create an interface called IOnUserDetailsReceivedListener with the method onUserDetailsReceived(UserDetails userDetails). The reason for this is that your download task will take some time. You need to get informed when it's done and that's what we use this interface for. This is called a listener pattern.

    public interface IOnUserDetailsReceivedListener {
        public void onUserDetailsReceived(UserDetails userDetails);
        public void onUserDetailsError();
    }
    
  3. Create a class Downloader that contains your AsyncTask and that has a method retrieveUserDetails(); or something. In that method, run the async task to download. When you get the data from the server, fill it into a new UserDetails(...) object and then call listener.onUserDetailsReceived(userDetails).

    public class UserDetailsDownloader {
        private IOnUserDetailsReceivedListener mListener;
    
        public UserDetailsDownloader(IOnUserDetailsReceivedListener listener) {
            mListener = listener;
        }
    
        public void downloadUserDetails() {
            //Execute the async task here. In it's onPostExecute, do       mListener.onUserDetailsReceived(userDetails).
        }
    
        private class DownloaderTask extends AsyncTask<String, Integer, UserDetails> {
            @Override
            protected UserDetails doInBackground(String... params) {
                //Download code
                //In downloading there might go stuff wrong. If so, return null as an easy method without any error handling.
                UserDetails userDetails = new UserDetails("downloadedUsername");
                return userDetails;
            }
    
    
            @Override
            protected void onPostExecute(UserDetails userDetails) {
                if(userDetails == null) {
                    if(mListener != null) {
                        //Something went wrong. Tell the listener.
                        mListener.onUserDetailsError();
                    }               
                } else {
                    if(mListener != null) {
                        //Cool! Lets pass the userDetails to the activity.
                        mListener.onUserDetailsReceiver(userDetails);
                    }
                }
            }   
        }
    }
    
  4. Let your activity implements IOnUserDetailsReceivedListener.

    public void UserActivity extends Activity implements IOnUserDetailsReceivedListener {
        private UserDetailsDownloader mUserDetailsDownloader;
    
        public void onCreate(...) {
            mUserDetailsDownloader = new UserDetailsDownloader(this);
            mUserDetailsDownloader.downloadUserDetails();
        }
    
        public void onUserDetailsReceived(UserDetails userDetails) {
            //Yeeh we received user data.
        }
    
        public void onUserDetailsError() {
            //Something went wrong. Tell the user?
        }
    }
    
  5. When your task is done, it'll call your Activities onUserDetailsReceived method and pass you the UserDetails value object with which you can then do what you want.

metter
  • 1,434
  • 11
  • 22
  • I made GetUserData an activity, because I read somewhere that "sharedPrefs = getApplicationContext().getSharedPreferences(APP_SHARED_PREFS, Context.MODE_PRIVATE);" cant run outside of an acitvity's on create. – Kaloyan Roussev Nov 01 '13 at 14:04
  • but Im thinking that If I pass the user Id to GetUserData I will not need to use the shared preferences, hence I will not need the GetUserData to be an activity – Kaloyan Roussev Nov 01 '13 at 14:05
  • 1
    @J.Kowalski You can certainly run it outside of `onCreate()`...you just need a `Context` to run it. So you could, say, pass `Activity Context` to a non-`Activity` class and use it that way. – codeMagic Nov 01 '13 at 14:07
  • 1
    Exactly. I'll edit my answer to give a quick overview on how to do what you want. – metter Nov 01 '13 at 14:09
  • 1
    this is an amazing answer, I will try to do that. Bit out of my league, but hey, thats the way I like to learn : ))) – Kaloyan Roussev Nov 01 '13 at 14:26
  • +1 nice thorough example and **explanations**, metter. @J.Kowalski [here is an answer with another example of using the interface in AsyncTask](http://stackoverflow.com/questions/18517400/inner-class-can-access-but-not-update-values-asynctask/18517648#18517648) in case it can help. Your code looks ok other than these suggestions. – codeMagic Nov 01 '13 at 16:47
  • there is one thing that I dont understand. How do I run the async task from within the UserDetailsDownloader.downloadUserDetails()? Do I call a class that extends AsyncTask? Or do I nest a class inside of it. – Kaloyan Roussev Nov 01 '13 at 18:00
  • Both works. Since the UserDetailsDownloader class has been written just to handle the download, it's ok to nest it. – metter Nov 01 '13 at 18:11
  • 1
    I have added an AsyncTask example and also added a way to tell the activity if something went wrong. Note that I didn't write this in an IDE so I hope there aren't too many typos. – metter Nov 01 '13 at 18:30
  • you are such a good person. I wish I could do something for you. Do you have an app that I can purchase, or something? – Kaloyan Roussev Nov 01 '13 at 23:05
2

I don't know if this is your only problem or not but too much for a comment. You shouldn't use runOnUiThread() in doInBackground()

 runOnUiThread(new Runnable() {
     public void run() {

     //this might cause some SHIT!!!!!!!!!!!! TEST IT!!!
     toastMaker.toast(getBaseContext(), configurationz.ERROR_MESSAGES_SIGNUP_DEVICE_UNABLE_TO_TAKE_PHOTOS, configurationz, Toast.LENGTH_LONG);

    }
});

this is why AsyncTask has onPostExecute() and its other methods...they all run on the UI Thread except for doInBackground()

Instead of return null, returnsuccessand depending on that value, do what you need to inonPostExecute()`.

Edit

onPostExecute() gets its parameter from what doInBackground() returns which is the third param in your declaration public class GetUserDataGetter extends AsyncTask<String, String, String>. So you can change that param or return a String to onPostExecute() from doInBackground().

AsyncTask Docs

codeMagic
  • 44,549
  • 13
  • 77
  • 93
  • hey I will definitelly try that. I took that approach from the accepted answer here that has 255 upvotes so I didnt think much about it... http://stackoverflow.com/questions/5161951/android-only-the-original-thread-that-created-a-view-hierarchy-can-touch-its-vi – Kaloyan Roussev Nov 01 '13 at 13:50
  • WOW! I can see why you would have thought it was ok but I don't see how it got that many votes...it defeats the purpose of `AsyncTask` and will most likely cause you problems in the future. I have seen it a lot causing people issues and it is completely unnecessary. – codeMagic Nov 01 '13 at 13:53
  • how do I return success when doInBackground is of the type String? And where is the "String result" that onPostExecute is taking coming from? – Kaloyan Roussev Nov 01 '13 at 13:54
  • 1
    I have made an edit. I hope that clears it up some. I will also post the link to the `AsyncTask` docs. – codeMagic Nov 01 '13 at 13:57
  • 1
    I just fully read that question and answer you linked to. The answerer wasn't suggesting putting in `doInBackground()` of `AsyncTask` but in a background `Thread`. While `doInBackground()` is a background `Thread`, it is part of a `Class` which has functions to run on the `UI Thread` whereas if you just run a new `Thread` it doesn't have these other methods but it does have `runOnUiThread()`. – codeMagic Nov 01 '13 at 14:06