2

I have an Android application written in Xamarin/C#. The MainActivity has a plot (using Oxyplot) and a ListView. When the app is launched, the ListView is populated with some information from a SQLite database. The plot is a barchart showing the quantities of the products shown in the ListView. When I click a button, a DialogFragment pops up and gives me a form to add a new item or to change an existing one. Say I change one item (for example the quantity) and this will update the record in the database.

The DialogFragment does not fill the whole space so you can see part of the MainActivity in the background. I want both the ListView and the plot on the MainActivity to update so that when the information is updated in the dialog fragment, I can see the plot and the ListView showing the updated information behind the open DialogFragment. And in general, when I dismiss the fragment I have the list in MainActivity already updated.

I know I can start a new intent for the MainActivity from the dialog fragment:

 var intent = new Intent(Activity, typeof(MainActivity));
 StartActivity(intent);

but what I want to achieve is some kind of background update of the plot and ListView in the MainActivity so that I can see them changing in background even when the DialogFragment is open.

Let me focus on the ListView update only. My MainActivity is:

public class MainActivity : Activity
{
    Button btn;
    ListView myList;
    private MyListViewAdapter adapter;
    private SQLiteConnection db;
    string dbPath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "dbProd.db3");

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView (Resource.Layout.Main);
        myList = FindViewById<ListView>(Resource.Id.mListView);
        db = new SQLiteConnection(dbPath);

        btn.Click += Btn_Click;
    }

    protected override void OnResume()
    {
        base.OnResume();
        updateList();        
    }

    private void Btn_Click(object sender, EventArgs e)
    {
        FragmentTransaction transaction = FragmentManager.BeginTransaction();
        dialog_form createProd = new dialog_form(db, myList, adapter);
        createProd.Show(transaction, "dialog fragment");
    }

    private void updateList()
    {
     // read info from db
        var table = from d in db.Table<dbProd>()
                        select d;

        prods = new List<dbProd>();

        foreach (var prod in table)
        {
            prods.Add(prod);
        }

        adapter = new MyListViewAdapter(this, prods, Resource.Layout.listview_row, db);
        myList.Adapter = adapter;
        adapter.NotifyDataSetChanged();
    }

}

So the information read from the db is put in a list of object dbProd (which have properties like name/quantity/price). This list is passed to my custom adapter (see below) and the ListView shows them. The button click opens a DialogFragment that has few EditText to input name, quantity and price.

I pass the ListView and the custom adapter to this DialogFragment with the hopeI can update them when the DialogFragment is still on, so that when it gets dismissed the ListView on MainActivity is already updated. The DialogFragment is:

class dialog_form : DialogFragment
{
    private MyListViewAdapter Adapter;
    private ListView MyList;
    private SQLiteConnection db;
    private EditText name;
    private EditText quantity;
    private EditText price;
    private Button saveButton;

    public dialog_form(SQLiteConnection Db, ListView myList, MyListViewAdapter adapter)
    {
        db = Db;
        MyList = myList;
        Adapter = adapter;
    }

    public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        base.OnCreateView(inflater, container, savedInstanceState);

        var view = inflater.Inflate(Resource.Layout.dialog_form, container, false);
        Dialog.Window.RequestFeature(Android.Views.WindowFeatures.NoTitle);

        name = view.FindViewById<EditText>(Resource.Id.name);
        quantity = view.FindViewById<EditText>(Resource.Id.quantity);
        price = view.FindViewById<EditText>(Resource.Id.price);
        saveButton = view.FindViewById<Button>(Resource.Id.buttonUpdate);
        saveButton.Click += SaveButton_Click;
        return view;
    }

    private void SaveButton_Click(object sender, EventArgs e)
    {
        db.Insert(new dbProd(name.Text, quantity.Text, price.Text));

        // with this I wanted to refresh the ListView in MainActivity, but it doesn't work
        MyList.Adapter = Adapter;
        Adapter.NotifyDataSetChanged();
        Adapter.updateAdapter();

        //var intent = new Intent(Activity, typeof(MainActivity));
        //StartActivity(intent);

        this.Dismiss();
    }   
}

The custom adapter and its updateAdapter() method are:

class MyListViewAdapter : BaseAdapter<dbProd>
{
    public List<dbProd> mItems;
    private Context mContext;
    private int mRowLayout;
    private List<dbProd> prods;
    private SQLiteConnection db;

    // Default constructor
    public MyListViewAdapter(Context context, List<dbProd> items, int rowLayout, SQLiteConnection Db)
    {
        mItems = items;
        mContext = context;
        mRowLayout = rowLayout;
        db = Db;
    }

    public void updateAdapter()
    {
        mItems.Clear();

        var table = from d in db.Table<dbProd>()
                        select d;

        prods = new List<dbProd>();

        foreach (var prod in table)
        {
            prods.Add(prod);
        }

        mItems.AddRange(prods));
        NotifyDataSetChanged();
    }

    // Tells how many rows are in the dataset
    public override int Count
    {
        get { return mItems.Count; }
    }

    // Return a row identifier
    public override long GetItemId(int position)
    {
        return position;
    }

    // Return the data associated with a particular row
    public override dbProd this[int position]
    {
        get { return mItems[position]; }
    }

    // Return a view for each row
    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        View row = convertView;
        if (row == null)
        {
            row = LayoutInflater.From(mContext).Inflate(Resource.Layout.listView_rowProd, null, false);
        }

        TextView itemName = row.FindViewById<TextView>(Resource.Id.itemName);
        itemName.Text = mItems[position].Code;

        TextView quantity = row.FindViewById<TextView>(Resource.Id.quantity);
        quantity.Text = mItems[position].Quantity;

        TextView price = row.FindViewById<TextView>(Resource.Id.price);
        price.Text = mItems[position].Price;

        return row;
    }
}

So basically the process is pretty simple. A ListView in MainAvtivity is showing information from a DB. When a DialogFragment is opened, the user can insert new information in the DB and when the DialogFragment is closed I expect the ListView in MainActivity to reflect already the changes. For this reason I thought about passing the ListView and its adapter to the DialogFragment so that I can call the updateAdapter() method and notify the ListView about the change in the underlying dataset. I am not sure what is wrong with the above, but it doesn't work. At the moment I can only start a new intent and basically recreate the MainActivity. This will naturally refresh the ListView. But I would like to avoid using the StartActivity(intent) approach, given it is slower.

opt
  • 477
  • 1
  • 10
  • 25
  • Hi, can you show the compete codes. Just the `MainActivity`, `MyAdapter`, and the layout, so I can reproduce your problem. – Robbit Mar 12 '18 at 07:54
  • Hi I added more code. Thanks. – opt Mar 15 '18 at 23:37
  • You need pass `prods` to your `dialog_form`, and in your `SaveButton_Click` method, you need add the data to your `prods`. So have you tried it? – Robbit Mar 16 '18 at 02:43
  • Hi, in the SaveButton_Click method I call Adapter.updateAdapter() which is responsible for clearing any existing item in the prods list and update with the new info from the DB. – opt Mar 16 '18 at 08:18
  • I pass data from Fragment to Activity by creating Interface. I can show you some Java code if you need. Hope that helps! – i_A_mok Apr 13 '18 at 02:51

3 Answers3

0

You should change the priority of your line

first your code should be like this

MyList.Adapter = MyAdapter;

MyAdapter.updateAdapter();

instead of assigning the adapter after the update

0

Carrying the instances of a listview and its adapter to your dialog fragment from my understanding of things would be a bad practice what I usually do for such implementations is some smart work.

In the onResume of my Activity which is like this :

    protected override void OnResume()
    {
        base.OnResume();
    }

I add update my listview adapter so that it has the latest data from my SQLite database then I assign that updated adapter to my listview and Voila it works.

Something like this to be precise:

    protected override void OnResume()
    {
        base.OnResume();
        _myListView.adapter=new myAdapterClass(); // in your case your adapter class with parameters.
        //Also your listview object must be a global object for this to work as expected.         
    }

You can also use adapter.NotifyDataSetChanged(); this will also do the same thing.

What this basically does is calls my adapter class again and updates my data.

One more thing you can do is carry your activity instance to your dialogfragment class

Public dialog_form (Activity activity)
{

 }

Then get this in a local variable with something like this

Public Activity _activity;

 Public dialog_form (Activity activity)
{
   _activity=activity;
 }

Then use this class instance to call your function that is in the activity for updation:

Public void svbtn(Sender s, EventArgs e)
{
    //Lines of code   ..... 
   _activity.yourUpdateFunction();
   this.Dismiss();
 }

In case it doesn't work let me know.

FreakyAli
  • 13,349
  • 3
  • 23
  • 63
  • Hi, thanks for your answer. I added the OnResume() to the MainActivity but when I am working on the dialogFragment and then I press a button to dismiss that window, the OnResume is never triggered. In the OnResume I added a small method that handles the list updating (i.e. refresh the SQLite tables and get the new data that are then passed to the list adapter). – opt Apr 14 '18 at 13:36
  • Add your activity class file here so i can have a look – FreakyAli Apr 14 '18 at 20:32
  • Hi, I added this to the MainActivity code as listed in my question. – opt Apr 14 '18 at 23:08
  • If I debug and set a breakpoint in the OnResume() of MainActivity, I can see that when a DialogFragment is dismissed the code never stops ij the OnResume() so apparently that is never executed when a DialogFragment is closed and I return to see only the MainActivity on the screen. – opt Apr 14 '18 at 23:10
  • That is happening because you are not using the appcompat library, i assume because i am pretty sure it gets called when you close a fragment – FreakyAli Apr 15 '18 at 07:14
  • @opt I have added one more way check that out it will surely work – FreakyAli Apr 15 '18 at 07:30
  • I am using Android.Support.V7.AppCompat in my MainActivity, although the using statement is not "highlighted" so apparently it is never required in my Activity. – opt Apr 15 '18 at 07:43
  • Because you are not using it wait i will give you a link for it in a min for now check the change in my answer – FreakyAli Apr 15 '18 at 07:48
  • I tried to pass the MainActivity to the dialogFragment but should I make everything in the MainActivity class "public static" in order to make it accessible from the dialogFragment? – opt Apr 15 '18 at 15:07
  • Also, not sure I understand why the OnResume is never invoked. If I use the theme you suggested this will also change the way my app looks? Is the problem coming from the fact that the dialogFragment I dismiss is only filling half of the screen so the MainActivity is already half visible in the baclgroind? – opt Apr 15 '18 at 15:09
  • you do not need to make it public static to access in dialogfragment you can directly access it when you activity instance secondly onResume is not called because you are not actually using the appcompat library also yes using theme will change the way your app looks but only in terms of color that is assigned there itself which you can easily change yourself – FreakyAli Apr 16 '18 at 06:32
  • I was asking because if I pass the MainActivity instance to the DialogFragment, I cannot access MainActivity's methods (in particular the updateList()). – opt Apr 16 '18 at 06:59
  • change the access specifier of that method from private to public and then you will be able to try it out – FreakyAli Apr 16 '18 at 07:00
0

UI of MainActivity for Listview will only change when the passed list values for its Listview-Adapter is changed.

Adapter.NotifyDataSetChanged() will only notify updates to Adapter. Unless the same list you passed is not changed it wont update UI. You have written complex code calling update methods everywhere and NotifyDataSetChanged() too.

Please note within Fragment you need to set the value of list values prods of MainActivity for which adapter is taking data.

db.Insert(new dbProd(name.Text, quantity.Text, price.Text)); is only updating DB, you need programattically instruct adapter that its changed by updating values passed and calling NotifyDataSetChanged.

No need to call updateAdapter repeatedly . Its redundant.

Make prods global to MainActivity

prods = new List<dbProd>(); declare this outside updateList

Write a method to update this when Fragment changes DB values

public void UpdateDBValuesForLV(List<dbprod> newValues)
{

        prods = newValues;
        adapter.NotifyDataSetChanged();

}

from Fragment update this.


Note : Do not update directly by setting global variable.

//after update retrive DB values again

prods = new List<dbProd>();

    foreach (var prod in table)
    {
        prods.Add(prod);
    }
((MyActivity) getActivity()).UpdateDBValuesForLV(prods);

Linked : Data between Activity - Fragment

Morse
  • 8,258
  • 7
  • 39
  • 64