Values duplicating when fetching data from database and displaying to ListView

  • Replies:8
  • Answered
ConzT
  • Forum posts: 6

Mar 20, 2016, 8:33:58 PM via Website

Hey everyone!
Im new to Android Developement and currently working on my first app that should give me the time between first time clicking a button and second time clicking the button and save it to a currently selected customer.(I know there are already tons of those applications out there, but since im still learning and want an app that can be usefull for myself i decided to try ;-))

Current Status of App:
I established a connection to da mySql Database using Volley and a local webservice.

It works to insert my customers and time stamps to the table, but when loading times for a specific customer i get a strange result in one case. I tried debugging it but the app keeps crashing on debugging without a message. When not debugging it doesnt crash but shows weird data.

To the problem:

In my main activity called "ZeitErfassen" i have a button to get an overview of all customers display in a ListView.
I create the Listview with a custom ArrayAdapter because i want to pass my objects to the next Activity where my customers are displayed.

So onCreate of the overview of customers i create a new arraylist and fill it with all customers from my database. this list i pass to my customadapter and then set it as my adapter of the Listview.
Now, when i click on an item, i call a php script and pass the customer_id to the query to fetch all times from database where customer_id = customer_id.

Now the part where i get "strange" data...

1.(Source:ZeitErfassen;Destination:AddCustomer) I create a new customer,example xyz, in the app, data gets passed to the database.

2.(Source:ZeitErfassen;Destination:DisplayCustomer) I call my overview for all customers where the ListView is filled with data as described above. At the end ob the List I see the customer i just created,xyz.

3.Go back to Main Activity(ZeitErfassen)

4.(Source:ZeitErfassen;Destination:DisplayCustomer)I open the overview for all customers again, and it shows my last created user two times! so last entry, xyz, entry before last, xyz!

After that, i can open the view as many times as i want, the customer never gets duplicated again!

The debugger stopps after step 2.

Now when i click on the new customers, it calls the script to fetch the times by customer_id.
One of the xyz entrys display the correct times from database.
The second one, i just found out, display the times where customer_id="". In the database the value for "" is 0.

I have no clue where the second customer suddenly appears from and debugging didnt help me either -.-
When i close the app an open it again, there ist just one entry for the user that was visible twice before closing the app. It doesnt duplicate on opening view...

I just discovered a second problem:
When starting the App, in the onCreate() method of my main activity(ZeitErfassen), the method customerFromDatabaseToList() should fill my LinkedList with all the customers and then the method createDropDown() should crate a spinner with all the customers from the list.
But when starting the app, I dont get any Data from by Database. Just when switching to another Activity and go back to the main.
The debugger didnt really help me. I want to debug the method twice, so first on create and then when switching back to the main activity. But the debugger stops after passing the breakpoint.(So i can debug the method once, but not twice...)

Here is my code..

Main Activity ZeitErfassen

public class ZeitErfassen extends AppCompatActivity {

public static LinkedList<Kunde> kunden = new LinkedList<Kunde>();
boolean running = false;
long startTime,endTime,totalTime;

private SharedPreferences app_preferences;
private SharedPreferences.Editor editor;
private TextView displayTime;
public Button startEndButton;
private ArrayAdapter<String> adapter;
private Spinner spinner;
public static Kunde selectedCustomer;




@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_zeit_erfassen);
    //Einstellungen laden
    app_preferences =  getApplicationContext().getSharedPreferences("MyPref", MODE_PRIVATE);
    startTime= app_preferences.getLong("startTime", 0);
    endTime = app_preferences.getLong("endTime", 0);
    running = app_preferences.getBoolean("running", false);
    displayTime = (TextView)findViewById(R.id.zeit_bei_Kunde);
    displayTime.setText((CharSequence) app_preferences.getString("zeitAnzeige", "Zeit bei Kunde"));
    startEndButton = (Button)findViewById(R.id.start_Timer);
    startEndButton.setText((CharSequence) app_preferences.getString("timerButton", "Start Timer"));
    DatabaseHelper.customerFromDatabaseToList(this);
    createDropDown();
    editor = app_preferences.edit();
    editor.commit();
}

public void onDestroy() {
    super.onDestroy();

    editor.putLong("startTime", startTime);
    editor.putString("zeitAnzeige", (String) displayTime.getText());
    editor.putString("timerButton", (String) startEndButton.getText());
    editor.putLong("endTime", endTime);
    editor.putLong("totalTime", totalTime);
    editor.putBoolean("running", app_preferences.getBoolean("running", false));
    editor.commit();
    this.finish();
}

public void onResume() {
    super.onResume();
 //   saveCustomers();
 //   createDropDown();
}


public void startTimer(View view) {
    editor = app_preferences.edit();

    if(running == false) {
        startTime = getTime();
        running = true;
        editor.putLong("startTime", startTime);
        startEndButton.setText("End Timer");
        displayTime.setText("Zeitstoppung läuft");
        editor.putString("zeitAnzeige", (String) displayTime.getText());
        editor.putString("timerButton", (String) startEndButton.getText());
        editor.putBoolean("running", true);
        editor.commit();

    } else {
        setSelectedCustomer();
        endTime = getTime();
        editor.putLong("endTime",endTime);
        totalTime = endTime - startTime;
        editor.putLong("totalTime", totalTime);
        displayTime.setText(formatTime(totalTime));
        editor.putString("zeitAnzeige", (String) displayTime.getText());
        startEndButton.setText("Start Timer");
        editor.putString("timerButton", (String) startEndButton.getText());
        running = false;
        editor.putBoolean("running", false);
        editor.commit();
        DatabaseHelper.timeToDatabase(String.valueOf(selectedCustomer.getId()),formatTime(totalTime),this);
    //    selectedCustomer.saveTimeToCustomer(selectedCustomer, formatTimeForCustomer(totalTime));

    }
}

public String formatTime(Long totalTime) {
    int hours   = (int) ((totalTime / (1000*60*60)) % 24);
    int minutes = (int) ((totalTime / (1000*60)) % 60);
    int seconds = (int) (totalTime / 1000) % 60;

    String time = (String.valueOf(hours) + ":" + String.valueOf(minutes) + ":" + String.valueOf(seconds));
    return time;
}
public String formatTimeForCustomer(Long totalTime) {
    StringBuilder time = new StringBuilder();

    Calendar cal = Calendar.getInstance();
    int year    = cal.get(Calendar.YEAR);
    int month   = cal.get(Calendar.MONTH);
    int day     = cal.get(Calendar.DAY_OF_MONTH);
    time.append((String.valueOf(year) + "." + String.valueOf(month) + "." + String.valueOf(day))).append(formatTime(totalTime));
    return time.toString();
}

public void  neuerKunde(View view) {
    Intent intent = new Intent(this, AddKunde.class);
    startActivity(intent);
}
public void kundenÜbersicht(View view) {
//    setSelectedCustomer();
    Intent intent = new Intent(this, DisplayCustomer.class);
    startActivity(intent);
}

public long getTime() {
    long millis = System.currentTimeMillis();
    return millis;
}

public void setSelectedCustomer() {
    if(kunden.size() > 0) {
        if (spinner.getSelectedItem().toString() != null) {
            String tempCustomer = spinner.getSelectedItem().toString();
            for (Kunde k : kunden) {
                if (k.getName().equals(tempCustomer)) {
                    selectedCustomer = k;
                }
            }
        }
    }
}


public void createDropDown() {
    /*File file = new File(this.getFilesDir(),"kunden.ser"); NOT USED BECAUSE DATABASE WORKS
    if(file.exists()) {
        Kunde.importFromFile(this);
    }*/
        if (kunden.size() > 0) {
            spinner = (Spinner) findViewById(R.id.chooseCustomer);
            // Create an ArrayAdapter using the string array and a default spinner layout
            adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, DisplayCustomer.namesOfCustomers());

            // Specify the layout to use when the list of choices appears
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            // Apply the adapter to the spinner
            spinner.setAdapter(adapter);
        }
}

}

DisplayCustomer(Where all customers are displayed with data from Database)

public class DisplayCustomer extends AppCompatActivity {
CustomerAdapter customerAdapter;
public ArrayAdapter<String> adapterCustomerView;
private ListView listCustomerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_customer);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    ArrayList<Kunde> customerList = getCustomerObjects();
    customerAdapter = new CustomerAdapter(this,customerList);
    listCustomerView = (ListView)findViewById(R.id.list_View_Customers);
    //  adapterCustomerView = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, namesOfCustomers());
    listCustomerView.setAdapter(customerAdapter);
    openCustomerDetails();
}


public static ArrayList<String> namesOfCustomers() {
    ArrayList<String> customerNames = new ArrayList<>();
    if(ZeitErfassen.kunden.size() > 0 ) {
        for (Kunde k : ZeitErfassen.kunden) {
            customerNames.add(k.getName());
        }
    }
    return customerNames;
}


public static ArrayList<Kunde> getCustomerObjects() {
    ArrayList<Kunde> customerList = new ArrayList<>();
    if(ZeitErfassen.kunden.size() > 0 ) {
        for (Kunde k : ZeitErfassen.kunden) {
            customerList.add(k);
        }
    }
    return customerList;
}


public void openCustomerDetails() {
    listCustomerView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Kunde kunde = new Kunde();
            kunde = (Kunde)listCustomerView.getItemAtPosition(position);
            Intent intent = new Intent(DisplayCustomer.this, DisplayDetailedCustomer.class);
            intent.putExtra("selectedCustomerObject",(Parcelable)kunde);
            startActivity(intent);
        }
    });
}

}

My CustomerAdapter to pass data from one intent to another.

public class CustomerAdapter extends ArrayAdapter<Kunde> {
public CustomerAdapter(Context context, ArrayList<Kunde> customerList) {
    super(context,0,customerList);
}

public View getView(int position, View convertView, ViewGroup parent) {
    //Data for this position
    Kunde kunde = getItem(position);
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.items_customer_layout, parent, false);
    }
    // Lookup view for data population
    TextView tvName = (TextView) convertView.findViewById(R.id.tvCustomerName);
    // Populate the data into the template view using the data object
    tvName.setText(kunde.getName());

    // Return the completed view to render on screen
    return convertView;
}

}

DatabaseHelper Class

public class DatabaseHelper {
public static RequestQueue requestQueue;
public static String host = "my ip, wasnt allowed to post it to precent spam ;-)";
public static final String insertUrl = host+"insertCustomer.php";
public static final String showUrl = host+"showCustomer.php";
public static final String insertTimeUrl = host+"insertTime.php";
public static final String showTimeUrl = host+"showTimes.php";

public static void customerFromDatabaseToList(final Context context)  {
    //Display customer from database
    requestQueue = Volley.newRequestQueue(context);
    final ArrayList<String> customerNames = new ArrayList<>();
    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, showUrl, new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            try {
                JSONArray customers = response.getJSONArray("customers");
                if(customers.length() > 0) {
                    for (int i = 0; i < customers.length(); i++) {
                        JSONObject customer = customers.getJSONObject(i);
                        String customerName = customer.getString("cus_name");
                        String customerAddress = customer.getString("cus_address");
                        int customerID = Integer.valueOf(customer.getString("cus_id"));
                        if (customerName != null && customerAddress != null) {
                            try {
                                Kunde k = new Kunde(customerName, customerAddress, customerID);
                                if (!listContainsObject(k)) {
                                    ZeitErfassen.kunden.add(k);
                                }
                            } catch (Exception e) {
                                showAlert("Fehler in customerFromDatabaseToListn!", "Fehler", context);
                            }
                        } else {
                            showAlert("Fehler in customerFromDatabaseToListn!", "Fehler", context);
                        }
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            error.printStackTrace();
        }
    });
    requestQueue.add(jsonObjectRequest);
}

public static boolean listContainsObject(Kunde cust) {
    for(Kunde k : ZeitErfassen.kunden) {
        if(k.getId() == cust.getId()) {
            return true;
        }
    }
    return false;
}

public static void timeToDatabase(final String customer_id, final String time_value, final Context context) {
    requestQueue = Volley.newRequestQueue(context);
    StringRequest request = new StringRequest(Request.Method.POST, DatabaseHelper.insertTimeUrl, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {

        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            showAlert("Fehler","Fehler bei Verbindung zur Datenbank",context);
        }
    }) {
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            Map<String,String> parameters = new HashMap<String,String>();
            parameters.put("customerid",customer_id);
            parameters.put("timevalue",time_value);
            return parameters;
        }
    };
    requestQueue.add(request);
};


public static ArrayList<String> timesFromDataBaseToList(final Context context,final int customer_id) {
    requestQueue = Volley.newRequestQueue(context);
    final String cus_id = String.valueOf(customer_id) ;
    final ArrayList<String> customerTimes = new ArrayList<>();

    StringRequest jsonObjectRequest = new StringRequest(Request.Method.POST, showTimeUrl, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            try {
                JSONObject object = new JSONObject(response.toString());
                JSONArray times = object.getJSONArray("customertimes");
                if (times.length() > 0) {
                    for (int i = 0; i < times.length(); i++) {
                        JSONObject jsonObject = times.getJSONObject(i);
                        String timeValue = jsonObject.getString("time_value");
                        if (timeValue != null) {
                            customerTimes.add(timeValue);
                        }
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(context,"Fehler beim Holen der Zeiten",Toast.LENGTH_LONG).show();
                error.printStackTrace();
            }
    }){
        protected Map<String, String> getParams() throws AuthFailureError {
            Map<String,String> parameters = new HashMap<String,String>();
            parameters.put("cus_id",cus_id);
            return parameters;
        }
    };
    requestQueue.add(jsonObjectRequest);
    return customerTimes;
};

}

DisplayDetailedCustomer / Display the times

public class DisplayDetailedCustomer extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_detailed_customer);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    Intent getCustomerParcable = getIntent();
    Kunde customer = getCustomerParcable.getExtras().getParcelable("selectedCustomerObject");

    TextView displayCustomerNameDetailed =(TextView) findViewById(R.id.detailedCustomerViewName);
    TextView displayCustomerAddressDetailed =(TextView) findViewById(R.id.detailedCustomerAddress);
    ListView timeListView = (ListView)findViewById(R.id.detailedTimeListView);
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, DatabaseHelper.timesFromDataBaseToList(this,customer.getId()));
    timeListView.setAdapter(adapter);
    displayCustomerNameDetailed.setText(customer.getName());
    displayCustomerAddressDetailed.setText(customer.getAdresse());
}

}

Kunde Class / Customer Class with interface Parcelable

public class Kunde implements Serializable,Parcelable {
private String name;
private String adresse;
private int id;
public LinkedList<String> zeiten;

public Kunde(String name, String adresse) throws Exception{
    setName(name);
    setAdresse(adresse);
    zeiten = new LinkedList<String>();
}

public Kunde(String name, String adresse,int id) throws Exception{
    setName(name);
    setAdresse(adresse);
    setId(id);
    zeiten = new LinkedList<String>();
}
public Kunde(){};


public void setId(int id) {
    this.id = id;
}
public int getId(){
    return id;
}
public void setName(String name) throws Exception {
    if(name != null) {
        this.name = name;
    } else throw new Exception("Name ist ungueltig! in setName");
}

public void setAdresse(String adresse) throws Exception{
    if(adresse != null) {
        this.adresse = adresse;
    }else throw new Exception("Adresse ist ungueltig! in setAdresse");
}

public String getName() {
    return name;
}

public String getAdresse() {
    return adresse;
}


public void saveZeit(Long totalTime) {
    zeiten.add(String.valueOf(totalTime));
}


public void saveTimeToCustomer(Kunde customer,String time){
   customer.zeiten.add(time);
}

//------------------------------------Parcelable Methods to pass Daata from one Intent to another----------------------------------------
@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(this.id);
    dest.writeString(this.name);
    dest.writeString(this.adresse);
//    dest.writeList(this.zeiten);
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<Kunde> CREATOR = new Parcelable.Creator<Kunde>() {
    public Kunde createFromParcel(Parcel in) {
        return new Kunde(in);
    }

    public Kunde[] newArray(int size) {
        return new Kunde[size];
    }
};

// example constructor that takes a Parcel and gives you an object populated with it's values
private Kunde(Parcel in) {
    LinkedList<String> zeiten = null;
    id = in.readInt();
    name = in.readString();
    adresse = in.readString();
}

}

Thanks for taking your time!!

Reply
itterN
  • Forum posts: 12

Mar 24, 2016, 10:32:36 AM via Website

First, your second problem: DatabaseHelper::customerFromDatabaseToList spawns a background task (or so it appears, I didn't set up a test environment to confirm) - as such, when ZeitErfassen::onCreate is executed the first time the background task is still pending and kunden is empty (nothing is displayed in the list).

Your first problem, based on a cursory overview, does not appear trivial. The second entry cannot represent a JSON result where customer.getString("cus_id") returned "" (an empty string), or Integer.valueOf would throw a NumberFormatException and the result would never be added to kunden. Log what happens in onResponse in the jsonObjectRequest object of customerFromDatabaseToList, specifically taking a look at the parsed customer data and when/what is added to kunden. If two results appear in the list of JSON results it should be a simple matter for you to fix the problem - if only one appears, however, I would guess some sort of race condition is occurring. In that case, synchronization would need to be added - alternatively, moving the processing of the JSON response to the UI thread would probably fix the problem.

Finally, I would strongly recommend you refactor ZeitErfassen. The onResponse method in the JsonObjectRequest should be used to populate your spinner directly, or indirectly through a call to runOnUiThread (or similar) depending on whether or not that method is guaranteed to run on the app's UI thread. The shared public static state should really be eliminated entirely. This will probably resolve both of the bugs you described above.

— modified on Mar 24, 2016, 10:38:31 AM

ConzT

Reply
itterN
  • Forum posts: 12

Mar 24, 2016, 11:25:38 AM via Website

Investigating further, it appears the RequestQueue used above is running onResponse on the UI thread so race conditions should not be a problem there. Log the values you extract from the JSON response and post the results here if the problem is not clear - I'll add another post if I notice another possible cause.

This isn't related to your question but you should be aware that storing a Parcelable object in a Bundle (like you do in openCustomerDetails) does not clone the object. The object you extract from the intent in DisplayDetailedCustomer will likely be the same object present in kunden - the bundle will only marshal/unmarshal the object when it has to. This can lead to obscure problems when the target activity starts editing the object fields.

ConzT

Reply
itterN
  • Forum posts: 12

Mar 24, 2016, 11:46:21 AM via Website

The problem is likely part of AddKunde; can you post that class? I'm guessing the AddKunde class adds an entry to kunden with an id of zero. What isn't clear is how ZeitErfassen's onCreate method is invoked in your list of actions there, however I'd imagine it's invoked at some point between the first and second time DisplayCustomer is launched leading to your duplicate entry. Please also clarify the actions you used to reproduce the bug, such as:

Luanch ZeitErfassen
  Launch AddKunde
    Add new user
  Back button
  Launch DisplayCustomer
    One entry displayed
  Back button
  Launch DisplayCustomer
    Two entries displayed
  Back button

ConzT

Reply
ConzT
  • Forum posts: 6

Mar 26, 2016, 10:10:03 PM via Website

Hey itterN, thanks for your replies and taking a look at my code! Sorry, that I didnt reply or took a look at this post. Just havent had a lot of time the last days!

You are a genius! Seems you really figured out my problems!

1st:

First, your second problem: DatabaseHelper::customerFromDatabaseToList spawns a background task (or so it appears, I didn't set up a test environment to confirm) - as such, when ZeitErfassen::onCreate is executed the first time the background task is still pending and kunden is empty (nothing is displayed in the list)

Your right about that, I didnt know that this would happen. I set a log message in the for loop where i add my customers to my list in the onResponse method. It prints out every log correctly! So i set a breakpoint at the method ZeitErfassen.kunden.add() and i saw that the Main Activity is already displayed before the method in the background finishes.

How can i fix this problem? So that the Activity waits untill all data is loaded?

2nd.
The problem is likely part of AddKunde; can you post that class? I'm guessing the AddKunde class adds an entry to kunden with an id of zero. What isn't clear is how ZeitErfassen's onCreate method is invoked in your list of actions there, however I'd imagine it's invoked at some point between the first and second time DisplayCustomer is launched leading to your duplicate entry. Please also clarify the actions you used to reproduce the bug, such as:

You were right about that to! In AddKunde i added the customer to the list again, so there were two entries: once from customerFromDatabaseToList() and once from AddKunde, in AddKunde the new customer was created with a constructor without an id and that is why it was empty.
So that problem is gone ;-)

I went over the AddKunde method and changed a little, so that i would first get the highest id from the database, then create the new customer with the id from database+1 and then insert it into the database.

Finally, I would strongly recommend you refactor ZeitErfassen. The onResponse method in the JsonObjectRequest should be used to populate your spinner directly, or indirectly through a call to runOnUiThread (or similar) depending on whether or not that method is guaranteed to run on the app's UI thread. The shared public static state should really be eliminated entirely. This will probably resolve both of the bugs you described above.

Im currently working on restructuring the spinner. I thought about using my CustomerAdapter i also used for the ListView to display my customers.
Is this a good idea?
What public static state do you mean from which method? I usually just declare my methods static when i need them in another class..

3rd
This isn't related to your question but you should be aware that storing a Parcelable object in a Bundle (like you do in openCustomerDetails) does not clone the object. The object you extract from the intent in DisplayDetailedCustomer will likely be the same object present in kunden - the bundle will only marshal/unmarshal the object when it has to. This can lead to obscure problems when the target activity starts editing the object fields.

Thanks for the info, I'll keep it in mind! :-) I didnt want to edit the objects, just wanted to have the object in my ListView, that i can display the Names but onClick() get data from database by the ID and not the name(for example if there are customers with the same name?)
Is it not a good idea using it that way? I just didnt know how solve it in another way....

Thanks again!

Here is the AddKunde() if you are still interested!

public class AddKunde extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add_kunde);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}



public void addKunde(View view) throws Exception {
    try {
        EditText nameField = (EditText) findViewById(R.id.customerName);
        String name = nameField.getText().toString();
        EditText addressField = (EditText) findViewById(R.id.addressField);
        String address = addressField.getText().toString();
        if(TextUtils.isEmpty(name)){
            nameField.setError("Ungültiger Name!");
            return;
        } else if(TextUtils.isEmpty(address)){
            addressField.setError("Ungültige Adresse");
            return;
        } else {
            try {
                int cust_id = Integer.valueOf(DatabaseHelper.getHighestCustomerIDFromDatabase(getApplicationContext()))+1;
                DatabaseHelper.customerToDatabaseWithId(getApplicationContext(), String.valueOf(cust_id), name, address);
                Kunde customer = new Kunde(name, address,cust_id);
                DatabaseHelper.customerFromDatabaseToList(this);
                Intent returnIntent = new Intent();
                //returnIntent.putExtra("dataChanged",true);
                //setResult(Activity.RESULT_OK, returnIntent);
            } catch (VolleyError e) {
                Toast.makeText(this,"Fehler: Keine Verbindung zur Datenbank!",Toast.LENGTH_SHORT).show();
            }
        }
    } catch (Exception e) {
        throw new Exception("Fehler in addKunde!");
    }
    finish();
}

Reply
itterN
  • Forum posts: 12

Mar 26, 2016, 11:47:43 PM via Website

How can i fix this problem? So that the Activity waits untill all data is loaded?

Adding a callback or similar would work there:

public class DatabaseHelper {
    public static void customerFromDatabaseToList(final Context context) {
        customerFromDatabaseToList(context, null);
    }

    public static void customerFromDatabaseToList(final Context context, final Runnable onSuccess)  {
        //Display customer from database
        requestQueue = Volley.newRequestQueue(context);
        final ArrayList<String> customerNames = new ArrayList<>();
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, showUrl, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    JSONArray customers = response.getJSONArray("customers");
                    if(customers.length() > 0) {
                        for (int i = 0; i < customers.length(); i++) {
                            JSONObject customer = customers.getJSONObject(i);
                            String customerName = customer.getString("cus_name");
                            String customerAddress = customer.getString("cus_address");
                            int customerID = Integer.valueOf(customer.getString("cus_id"));
                            if (customerName != null && customerAddress != null) {
                                try {
                                    Kunde k = new Kunde(customerName, customerAddress, customerID);
                                    if (!listContainsObject(k)) {
                                        ZeitErfassen.kunden.add(k);
                                    }
                                } catch (Exception e) {
                                    showAlert("Fehler in customerFromDatabaseToListn!", "Fehler", context);
                                }
                            } else {
                                showAlert("Fehler in customerFromDatabaseToListn!", "Fehler", context);
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }

                if (onSuccess != null)
                    onSuccess.run();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        });
        requestQueue.add(jsonObjectRequest);
    }
}

public class ZeitErfassen extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_zeit_erfassen);
        //Einstellungen laden
        app_preferences =  getApplicationContext().getSharedPreferences("MyPref", MODE_PRIVATE);
        startTime= app_preferences.getLong("startTime", 0);
        endTime = app_preferences.getLong("endTime", 0);
        running = app_preferences.getBoolean("running", false);
        displayTime = (TextView)findViewById(R.id.zeit_bei_Kunde);
        displayTime.setText((CharSequence) app_preferences.getString("zeitAnzeige", "Zeit bei Kunde"));
        startEndButton = (Button)findViewById(R.id.start_Timer);
        startEndButton.setText((CharSequence) app_preferences.getString("timerButton", "Start Timer"));
        DatabaseHelper.customerFromDatabaseToList(this, new Runnable() {
            @Override
            public void run() {
                createDropDown();
            }
        });
        // the list can still be populated here as well if desiered
        createDropDown();
        editor = app_preferences.edit();
        editor.commit();
    }
}

This may "flicker" when the list is refreshed in the callback; I'm not certain whether or not the list views are preserved when the adapter is swapped. If this causes problems set the list adapter once in onCreate, store the list used by the list's adapter as a private member variable of ZeitErfassen, and update the list (followed by a call to adapter.notifyDataSetChanged()) as required - most tutorials covering ListView should introduce those operations if that's unclear.

Im currently working on restructuring the spinner. I thought about using my CustomerAdapter i also used for the ListView to display my customers.
Is this a good idea?

I'd avoid that - an ArrayAdapter is sufficient there and you might want to add information to your ListView display in the future.

What public static state do you mean from which method? I usually just declare my methods static when i need them in another class..

I was referring to the public static kunden variable - a private instance variable in the activities that require it (along with a number of interface changes) would probably suffice there. That sort of an approach would likely reduce the coupling between your classes - that said, depending on your project goals that might not matter to you :-)

Thanks for the info, I'll keep it in mind! :-) I didnt want to edit the objects, just wanted to have the object in my ListView, that i can display the Names but onClick() get data from database by the ID and not the name(for example if there are customers with the same name?)
Is it not a good idea using it that way? I just didnt know how solve it in another way....

No, I wouldn't bother changing anything provided you're not altering the object's fields. If that changes simply wrap the object you're passing to the target activity (or similar) in a method that explicitly clones the Parcelable object:

public abstract class ParcelUtility {
    private ParcelUtility () {}

    public static <T extends Parcelable> T clone (T value) {
        return clone(value, 0);
    }

    public static <T extends Parcelable> T clone (T value, int flags) {
        if (value == null)
            return null;

        final Parcel parcel = Parcel.obtain();
        try {
            parcel.writeParcelable(value, flags);
            parcel.setDataPosition(0);
            return parcel.readParcelable(value.getClass().getClassLoader());
        } finally {
            parcel.recycle();
        }
    }
}

Hey itterN, thanks for your replies and taking a look at my code! Sorry, that I didnt reply or took a look at this post. Just havent had a lot of time the last days!

No problem, I'm glad you found them helpful :-)

ConzT

Reply
ConzT
  • Forum posts: 6

Mar 27, 2016, 5:15:08 PM via Website

Adding a callback or similar would work there:

Hey ittN, thanks for your helpful answer! :D:D :D(hug)
I added the new method not in onCreate() but in onResume() in case I add a new Customer, so that the list gets refreshed when switching back to the main activity.
I also think about checking if the size of my list increased before calling the method so that it wont be called everytime when switching to main activity.

I was referring to the public static kunden variable - a private instance variable in the activities that require it (along with a number of interface changes) would probably suffice there. That sort of an approach would likely reduce the coupling between your classes - that said, depending on your project goals that might not matter to you :-)

Ahhh ok, now I know what you mean ;-) I'm thinking about improving that, but in the meantime I'll just keep it like that...

No, I wouldn't bother changing anything provided you're not altering the object's fields. If that changes simply wrap the object you're passing to the target activity (or similar) in a method that explicitly clones the Parcelable object:

public abstract class ParcelUtility {
    private ParcelUtility () {}

    public static <T extends Parcelable> T clone (T value) {
        return clone(value, 0);
    }

    public static <T extends Parcelable> T clone (T value, int flags) {
        if (value == null)
            return null;

        final Parcel parcel = Parcel.obtain();
        try {
            parcel.writeParcelable(value, flags);
            parcel.setDataPosition(0);
            return parcel.readParcelable(value.getClass().getClassLoader());
        } finally {
            parcel.recycle();
        }
    }
}

Man thanks for that class, i didn' know that there already existed one for cloning parcelable objects! I'll keep that in mind, definitly going to use that in the future :-)

Thousand Thanks again, i had sleepless nights because of that problem :D !!!! :-)

Now that it is solved i can continue to expand the functions of my project :-)

Reply
ConzT
  • Forum posts: 6

Mar 27, 2016, 5:46:59 PM via Website

Hey one more question ;-)

I had a similar problem to display the times for each customer, that sometimes they wouldnt be displayed though there was data in the database.

I implemented the callback here aswell, just wanted to ask you if right how i implemented it ;-)

DisplayCustomer Activity:

public class DisplayCustomer extends AppCompatActivity {
CustomerAdapter customerAdapter;
public ArrayAdapter<String> adapterCustomerView;
private ListView listCustomerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_customer);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    ArrayList<Kunde> customerList = getCustomerObjects();
    customerAdapter = new CustomerAdapter(this,customerList);
    listCustomerView = (ListView)findViewById(R.id.list_View_Customers);
    listCustomerView.setAdapter(customerAdapter);
    openCustomerDetails();
}

public void openCustomerDetails() {
    listCustomerView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Kunde kunde = new Kunde();
            kunde = (Kunde)listCustomerView.getItemAtPosition(position);
            Intent intent = new Intent(DisplayCustomer.this, DisplayDetailedCustomer.class);
            intent.putExtra("selectedCustomerObject",(Parcelable)kunde);
            startActivity(intent);
        }
    });
}

DisplayDetailedCustomer Activity:

public class DisplayDetailedCustomer extends AppCompatActivity {
ArrayAdapter<String> adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_detailed_customer);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    Intent getCustomerParcable = getIntent();
    Kunde customer = getCustomerParcable.getExtras().getParcelable("selectedCustomerObject");

    TextView displayCustomerNameDetailed =(TextView) findViewById(R.id.detailedCustomerViewName);
    TextView displayCustomerAddressDetailed =(TextView) findViewById(R.id.detailedCustomerAddress);
    final ListView timeListView = (ListView)findViewById(R.id.detailedTimeListView);
    adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, DatabaseHelper.timesFromDataBaseToList(this, customer.getId(), new Runnable() {
        @Override
        public void run() {
            if(adapter!= null) {
                timeListView.setAdapter(adapter);
            }
        }
    }));
    displayCustomerNameDetailed.setText(customer.getName());
    displayCustomerAddressDetailed.setText(customer.getAdresse());
}

}

And my DataBase method

public static ArrayList<String> timesFromDataBaseToList(final Context context,final int customer_id) {
    return timesFromDataBaseToList(context,customer_id,null);
}
public static ArrayList<String> timesFromDataBaseToList(final Context context,final int customer_id, final Runnable onSuccess) {
    requestQueue = Volley.newRequestQueue(context);
    final String cus_id = String.valueOf(customer_id) ;
    final ArrayList<String> customerTimes = new ArrayList<>();

    StringRequest jsonObjectRequest = new StringRequest(Request.Method.POST, showTimeUrl, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            try {
                JSONObject object = new JSONObject(response.toString());
                JSONArray times = object.getJSONArray("customertimes");
                if (times.length() > 0) {
                    for (int i = 0; i < times.length(); i++) {
                        JSONObject jsonObject = times.getJSONObject(i);
                        String timeValue = jsonObject.getString("time_value");
                        if (timeValue != null) {
                            customerTimes.add(timeValue);
                        }
                    }
                }
            } catch (JSONException e) {
                Toast.makeText(context,"Fehler beim holen der Daten!Code:X6",Toast.LENGTH_SHORT).show();
            }
            if(onSuccess != null) {
                onSuccess.run();
            }
        }
    }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(context,"Fehler beim Holen der Zeiten, keine Verbindung zur Datenbank!Code: X7",Toast.LENGTH_LONG).show();
                error.printStackTrace();
            }
    }){
        protected Map<String, String> getParams() throws AuthFailureError {
            Map<String,String> parameters = new HashMap<String,String>();
            parameters.put("cus_id",cus_id);
            return parameters;
        }
    };
    requestQueue.add(jsonObjectRequest);
    return customerTimes;
};

Reply
itterN
  • Forum posts: 12

Apr 1, 2016, 4:52:59 AM via Website

With respect to the callback code you added, it looks like that will probably work the way you intended. One small alteration - the adapter should be notified that it's dataset has changed there, not reassigned:

if(adapter!= null) {
    // timeListView.setAdapter(adapter);
    adapter.notifyDataSetChanged();
}

Reply