Automatically refresh the display of an ImageView without calling the whole activity again

TBMSamTBMSam Member ✭✭

Hello altogether,

In my Xamarin.Android-App, when opening a special page (activity), there is a table displayed on create.
A ListAdapter defines the certain fields within that table.
I have a function which gets me an external int. The int is amongst other data displayed in that table.
But this function is only called once (on create).
How can I call this function multible time during runtime within a certain time interval? I want that if this received int changes, the display of the int in the table changes as well without calling the whole activity again.

Can anyone maybe help me and give me a hint how to do this? That would really be very kind!

Please let me share some code, so it's maybe easier for you to understand me:

Resource.Layout.Activity:

<LinearLayout>
    [...]
    <ListView
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/ListView" />
</LinearLayout> 

Activity:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Activity);
    [...]    
    var lv = FindViewById<ListView>(Resource.Id.ListView);
    lv.Adapter = new ListAdapter(this, Resource.Layout.List, list.CurrentList, Intent.GetStringExtra("ServerIP"));
}

Resource.Layout.List:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout 
xmlns:android="http://schemas.android.com/apk/res/android"     
android:orientation="horizontal"     
android:layout_width="fill_parent"     
android:layout_height="fill_parent">

[...]

<ImageView         
    android:id="@+id/ImageView" />

</RelativeLayout>

class ListAdapter : ArrayAdapter

public ListAdapter(Context Context, int ListId, List<Geraet> list, string serverip) : base(Context, ListId, Geraete)
{ 
    this.List = list; 
}

[...]

public override View GetView(int position, View convertView, ViewGroup parent)
{
    View v = convertView;
    if (v == null)
    {
        LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
        v = inflater.Inflate(Resource.Layout.List, parent, false);
    }
    if (Get_Status(List[position]) == 1)
    {
        v.FindViewById<ImageView>(Resource.Id.ImageView).SetImageResource(Resource.Drawable.green);
    }
    if (Get_Status(List[position]) != 1)
    {
        v.FindViewById<ImageView>(Resource.Id.ImageView).SetImageResource(Resource.Drawable.red);
    }
}

[...]

private int Get_Status(Geraet geraet)
{
    var request = HttpWebRequest.Create(string.Format(Constants.StatusPath, Serverip, Constants.WebservicePort, geraet.Ip));
    request.ContentType = "application/json";
    request.Method = "GET";

    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
    {
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            var content = reader.ReadToEnd();

            var result = System.Text.RegularExpressions.Regex.Replace(content, @"[^0-9]", "");

            if (result == string.Empty)
            {
                return -2;
            }
            else
            {
                return Int32.Parse(result);
            }
        }
    }
}

Thanks for answers in advance and best regards

Answers

  • jezhjezh Member, Xamarin Team Xamurai
    edited January 10

    You can try to use the System.Timers.Timer to implement the function.
    You can add the following code in the activity

        public void startTimer() {
            System.Timers.Timer Timer1 = new System.Timers.Timer();
            Timer1.Start();
            Timer1.Interval = 1000;
            Timer1.Enabled = true;
            //Timer1.Elapsed += OnTimedEvent;
            Timer1.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
            {
                //Timer1.Stop();
                RunOnUiThread(() =>
                {                  
                    myAdapter.NotifyDataSetChanged();   // the code here is used to update the ListView
                });
                //Delete time since it will no longer be used.
                //Timer1.Dispose();
            };
            Timer1.Start();
        }
    

    Note: add above method startTimer();just as follows:

     protected override void OnCreate(Bundle bundle)
     {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Activity);
        [...]    
        var lv = FindViewById<ListView>(Resource.Id.ListView);
        lv.Adapter = new ListAdapter(this, Resource.Layout.List, list.CurrentList, Intent.GetStringExtra("ServerIP"));
    
        startTimer();//    call the method here
      }
    

    Note : when you don't want the timer to function you can call the following code to release the timer.

                timer.Dispose();
                timer = null;
    
  • TBMSamTBMSam Member ✭✭
    edited January 10

    Hello jezh and thank you really really much for your answer and your efforts helping me.

    I tried to add the code in the activity as recommended, but unfortunately he does not know neither count nor settingItems. What can I do to get him to know these variables? May I just define them?
    Besides that, you say myAdapter.NotifyDataSetChanged(); // the code here is used to update the ListView. As he did not know myAdapter as well, I thought that I need to change this to my personal adapter I have for the ListView, so I changed it to ListAdapter adapter = new ListAdapter(); adapter.NotifyDataSetChanged();. Hope that was correct. One question to this: Does this update the whole ListView? Or only one line (especially one field within that row) as it should be?

    Best regards

  • jezhjezh Member, Xamarin Team Xamurai

    @TBMSam
    Sorry, I fogot to delete the code of my app .and I have updated it .
    The code adapter.NotifyDataSetChanged(); refresh the whole ListView, and we could not determine which rows should be updated because we need get the value of Get_Status(List[position]) from HttpWebRequest a certain time interval.
    So we need refresh the whole ListView .

    Hope it can hely you.

  • TBMSamTBMSam Member ✭✭
    edited January 10

    @jezh Thank you for updating your code.

    To get my own, personal adapter instead of myAdapter, I made the following, please see this excerpt of my Activity:

    public class MyActivity : Activity
    {                
        ListView lv; //see?^^
    
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Unlock);  
            // (...)
            lv = FindViewById<ListView>(Resource.Id.ListView);
            lv.Adapter = new ListAdapter(this, Resource.Layout.List, MyListClass.CurrentList, serverip);
            startTimer();
            // (...)
        }
    
        public void startTimer()
            {
                    System.Timers.Timer Timer = new System.Timers.Timer();
                    Timer.Start();
                    Timer.Interval = 1000;
                    Timer.Enabled = true;
                    Timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
                    {
                        //Timer1.Stop();
                        RunOnUiThread(() =>
                        {
                                lv.Adapter.NotifyDataSetChanged();
                        });
                        //Delete time since it will no longer be used.
                        //Timer.Dispose();
                    };
                    Timer.Start();
            }
    

    Unfortunately, there is again an error occuring. He underlines NotifyDataSetChanged(); saying "IListAdapter" does not contain a definition for "NotifyDataSetChanged();" and an there was no available NotifyDataSetChanged-Extention-Method found, which accepts first argument of type "IListAdapter" (maybe a using-directive or assembly-reference is missing). Is ListView the correct class for defining the adapter in the beginning? Or is something different needed? Because in the beginning, I cannot write var, I have to specify the type of variable.

    In Addition: Sorry, I didn't get why we could not determine which row should be updated. Refreshing the whole List each time will cause many problems, I will try to describe the scenario to get you an insight what's this all about, maybe it's easier than for you to get what I am talking about: The table I have spoken of in the very beginning shows a list of machines and their state (so if machine is online or not). Therefore I am trying to in the first step reach and in the second step ask them via HTTPWebRequest about their state. If possible and they are available, the machines send 1. So if one state of one machine changes (not reachable or sending something different than 1), and we update the whole list, we have to get each machine from database and ask for their state again right? Can you understand what/how I am thinking?
    That's why ideally only the display of the state of the one machine changed should be refreshed, not the whole list each time a state changes, due to capacity / computing power / network load limitations. That must be possible anyhow come on^^

    Thanks again for taking care of me,

    Best Greetings

  • jezhjezh Member, Xamarin Team Xamurai
    edited January 11

    In my sample, the myAdapter is a variable of 'MyAdapter ', and the class MyAdapter extends from BaseAdapter just as follows:

    class MyAdapter : BaseAdapter
    

    So , it can be used like the code I posted.
    And since your ListAdapter is extends from ArrayAdapter

         class ListAdapter : ArrayAdapter{....}
    

    it should use the following method to refresh the Listview

      public void startTimer()
        {
                System.Timers.Timer Timer = new System.Timers.Timer();
                Timer.Start();
                Timer.Interval = 1000;
                Timer.Enabled = true;
                Timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
                {
                    //Timer1.Stop();
                    RunOnUiThread(() =>
                    {  // key code
                          lv.Adapter = new ListAdapter(this, Resource.Layout.List, list.CurrentList, Intent.GetStringExtra("ServerIP"));
                    });
                    //Delete time since it will no longer be used.
                    //Timer.Dispose();
                };
                Timer.Start();
        }
    

    or use the following code(assume you define a variable listViewAdapter)

            ArrayAdapter listViewAdapter;
    

    and Initial it :

      listViewAdapter = new ListAdapter(this, Resource.Layout.List, list.CurrentList, Intent.GetStringExtra("ServerIP"));
      lv.Adapter  = listViewAdapter;
    

    then you can use the following method to refresh it

          public void startTimer()
        {
                System.Timers.Timer Timer = new System.Timers.Timer();
                Timer.Start();
                Timer.Interval = 1000;
                Timer.Enabled = true;
                Timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
                {
                    //Timer1.Stop();
                    RunOnUiThread(() =>
                    {  // key code
                          listViewAdapter.Clear();
                          foreach (var item in list.CurrentList)
                         {
                            listViewAdapter.Insert(list.CurrentList, listViewAdapter.Count);
                          }
                        listViewAdapter.NotifyDataSetChanged();
                    });
                    //Delete time since it will no longer be used.
                    //Timer.Dispose();
                };
                Timer.Start();
        }
    

    Of course , you can modify your ListAdapter to extends from BaseAdapter , and use it in my way.

    Hope it can help you.

  • TBMSamTBMSam Member ✭✭
    edited January 11

    Hm okay thank you, I got what you want to say. I have been trying several stuff myself since yesterday, and found out that alternatively when doing a typecast (((ListAdapter)lv.Adapter).NotifyDataSetChanged();) everything seem to work as well (emphasis: seem^^). The emphasis because now the app is extreeeeeeeemly slowly :( Can we make that whole refreshment stuff async anyhow? :X

  • jezhjezh Member, Xamarin Team Xamurai

    This is another question , you can create a new case to ask for help .

    If my answer is useful for you ,could you please mark it as answer?

  • TBMSamTBMSam Member ✭✭

    Hello and thank you again for your help @jezh,

    I got the point you are talking about. Unfortunately I am pretty new in programming, so please excuse me having trouble implementing everything.

    I found out, when modifying the startTimer()-function to

        public void startTimer()
        {
            System.Timers.Timer Timer1 = new System.Timers.Timer();
            Timer1.Start();
            Timer1.Interval = 1000;
            Timer1.Enabled = true;
            Timer1.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
            {
                RunOnUiThread(() =>
                {
                    ((ListAdapter)lv.Adapter).NotifyDataSetChanged();
                });
            };
            Timer1.Start();
        }
    

    everything seem to work (emphasis: seem xD). Because now the app is extreeeeeeeeemly slow. Can we make that whole stuff async anyhow? Or I don't know what can we do to optimize the code? :X

    Let me briefly describe the functionality of the app, so maybe you get an overview what I am trying to do.

    As written in the beginning, there is a table displayed on create. The table itself is not that huge. It contains maybe maximum 40 or 50 entries. On create, the app fills this list from a database. So far so good. But each entry is one physical machine in behind. So the table which is displayed upon activity just shows these machines and their state.

    Please see the following example:

    Machine Number (Key) Description / Name Online?
    123456789 "machine number 1" no
    987654321 "machine number 2" yes
    123123123 "machine number 3" no

    The mentioned function which gets me an external int always asks the machine whether it's reachable or not. If yes, the machine sends 1, if not the function runs into a timeout. Additionally, a machine can be reachable, but not useable. In this case, the machine sends something different than 1.

    So most likely, at least I think so, if machine not reachable, waiting on the timeout now makes the app that slow.

    As I tried to describe I am simply trying to refresh the state-display of those machines, so if they are online (actually useable) or not.

    Hoping that that was anyhow understandable, please excuse my language difficulties.

    Best regards

Sign In or Register to comment.