I have created a command prompt widget for Android using Xamarin, and I'm trying to figure out how to set up my ListViewFactory class and RemoteViewsService class to have individual data per widget.
From my understanding, the RemoteViewsService class returns the ListViewFactory object to be displayed to the user. The problem is I have user input for my list, and I don't know how to get reference to the ListViewFactory object that is returned from the RemoteViewsService.
WidgetProvider:
namespace CommAndroid
{
[BroadcastReceiver(Label = "CommAndroid", Exported = false, Enabled = true, Name = "com.company.CommAndroid.WidgetProvider",Icon ="@mipmap/terminalappicon")]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE" })]
[MetaData("android.appwidget.provider", Resource = "@xml/my_widget")]
//Class to inflate and show the widget
public class WidgetProvider : AppWidgetProvider
{
//Initialize Global Vars for Class
public static ListViewFactory listViewFactory;
private Handler handler;
//Update method for a widget
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
base.OnUpdate(context, appWidgetManager, appWidgetIds);
foreach (int appwidgetId in appWidgetIds)
{
// Build the remote views for the widget
var widgetView = BuildRemoteViews(context);
// Update all instances of the widget with the new remote views
appWidgetManager.NotifyAppWidgetViewDataChanged(appwidgetId, Resource.Id.listView1);
appWidgetManager.UpdateAppWidget(appwidgetId, widgetView);
}
}
//Build the widget view, returns to the update method
public RemoteViews BuildRemoteViews(Context context)
{
//Initialize our widget view, basically pointing to our main widget layout xml
var widgetView = new RemoteViews(context.PackageName, Resource.Layout.widget_layout);
//Initializing an intent of our WidgetRemoteViewService, to handle the creation of our list view factory
//Instructing Widget to use our WidgetRemoteViewService with our ListView
Intent listViewIntent = new Intent(context, typeof(WidgetRemoteViewService));
widgetView.SetRemoteAdapter(Resource.Id.listView1, listViewIntent);
//Initializing an intent for the main widget button click
var clickIntent = new Intent(context, typeof(WidgetProvider));
clickIntent.SetAction("com.company.CommAndroid.WIDGET_BUTTON_CLICK");
// Initializing a new Intent for the delete button click action
var deleteIntent = new Intent(context, typeof(WidgetProvider));
deleteIntent.SetAction("com.company.CommAndroid.DELETE_BUTTON_CLICK");
// Initializing pending intents for the button click actions
var pendingIntent = PendingIntent.GetBroadcast(context, 5555533, clickIntent, PendingIntentFlags.Mutable);
var pendingDeleteIntent = PendingIntent.GetBroadcast(context, 55522111, deleteIntent, PendingIntentFlags.Mutable);
// Associating the pending intents with the respective buttons in the widget layout
widgetView.SetOnClickPendingIntent(Resource.Id.delete_button, pendingDeleteIntent);
widgetView.SetOnClickPendingIntent(Resource.Id.widget_button, pendingIntent);
Intent itemClickIntent = new Intent(context, typeof(WidgetProvider));
itemClickIntent.SetAction("com.company.CommAndroid.LIST_ITEM_CLICK");
PendingIntent itemClickPendingIntent = PendingIntent.GetBroadcast(context, 3434514, itemClickIntent, PendingIntentFlags.Mutable);
widgetView.SetPendingIntentTemplate(Resource.Id.listView1, itemClickPendingIntent);
// ...
return widgetView;
}
//On Deleted
public override void OnDeleted(Context context, int[] appWidgetIds)
{
base.OnDeleted(context, appWidgetIds);
}
//Method to update the listview in widget
private async void updateListView(Context context, RemoteViews views, bool isDelete, string command)
{
//Create a new list factory and view for our widget
//Create an AppWidgetManager, reference component name, and get appWidgetIds
listViewFactory = new ListViewFactory(context);
var widgetView = views;
AppWidgetManager appWidgetManager = AppWidgetManager.GetInstance(context);
ComponentName componentName = new ComponentName(context, Java.Lang.Class.FromType(typeof(WidgetProvider)).Name);
int[] appWidgetIds = appWidgetManager.GetAppWidgetIds(componentName);
//Checks bool parameter to see if user wants to clear list
if (isDelete != true)
{
//Initialize string result that is returned from querying the command through class TerminalCommands
//Add the command itself, and the results to the list
string result = await TerminalCommands.queryCommand(command, context, appWidgetManager, appWidgetIds, widgetView);
listViewFactory.addCommand("CMD: " + command, result);
//Create Handler to facilitate Self Scrolling of Listview
//Set Scroll position, partially update app widget
//Don't know why this works instead of just setting scroll position, but hey it's android development
handler = new Handler(Looper.MainLooper);
handler.PostDelayed(async() =>
{
widgetView.SetScrollPosition(Resource.Id.listView1, ListViewFactory.items.Count - 1);
appWidgetManager.PartiallyUpdateAppWidget(appWidgetIds, widgetView);
}, 500);
}
//Bool parameter for user clearing list
else
{
listViewFactory.clearList();
widgetView.SetEmptyView(Resource.Id.listView1, Resource.Id.empty_view);
}
//Update the widget view
appWidgetManager.NotifyAppWidgetViewDataChanged(appWidgetIds, Resource.Id.listView1);
appWidgetManager.UpdateAppWidget(componentName, views);
}
//Method for receiving broadcasts from broadcast receiver
public override void OnReceive(Context context, Intent intent)
{
//Initialize view for our widget layout
base.OnReceive(context, intent);
//Rebuild the view, re-setting the intents ---> Goal is to fix bug where widget stops working after some time
//Either this or settings flags to mutable
var widgetView = BuildRemoteViews(context);
//Command Click
if (intent.Action == "com.company.CommAndroid.WIDGET_BUTTON_CLICK")
{
//Create a new intent of activity InputProvider
//Start the input provider activity
Intent inputIntent = new Intent(Application.Context, typeof(InputProvider));
inputIntent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(inputIntent);
}
//Clear Click
else if (intent.Action == "com.company.CommAndroid.DELETE_BUTTON_CLICK")
{
updateListView(context, widgetView, true, "");
}
//User input
else if (intent.Action == "com.company.CommAndroid.USER_INPUT_SUBMITTED")
{
string command = intent.GetStringExtra("user_input");
updateListView(context, widgetView, false, command);
}
else if(intent.Action == "com.company.CommAndroid.LIST_ITEM_CLICK")
{
Log.Debug("hi", "we got here");
string commandText = intent.GetStringExtra("command_text");
}
}
}
}
ListViewFactory:
namespace CommAndroid
{
public class ListViewFactory : Java.Lang.Object, RemoteViewsService.IRemoteViewsFactory
{
//Globals
private Context context;
public static List<string> items;
public static List<string> results;
//Constructor to create list view factory
public ListViewFactory(Context context)
{
this.context = context;
if (items == null)
items = new List<string>();
if (results == null)
results = new List<string>();
if(items.Count == 0 && results.Count == 0)
{
items.Add("CMD: ");
results.Add("");
}
}
public void OnCreate()
{
// Initialize your data source
}
public void removeLast()
{
items.RemoveAt(items.Count - 1);
results.RemoveAt(results.Count - 1);
}
//Add's a command to the listview, removes the prior placeholder CMD text
public void addCommand(string command,string result)
{
if (items == null || items.Count < 0)
{
items = new List<string>();
}
removeLast();
items.Add(command);
results.Add(result);
items.Add("CMD: ");
results.Add("");
}
//Clear the lists
public void clearList()
{
items.Clear();
results.Clear();
items.Add("CMD: ");
results.Add("");
}
public void OnDestroy()
{
// Cleanup resources if needed
}
public int Count => items.Count;
//Method to create and set the listview view
public RemoteViews GetViewAt(int position)
{
// Create a RemoteViews object for each item in the data source, Referencing our Layout for our List View
RemoteViews remoteViews = new RemoteViews(context.PackageName, Resource.Layout.listview_layout);
//Check's if there is items, iterates until there is none
if (items != null && items.Count > position)
{
//Sets text in listview to command items and result items
//Also checks the commands text to determine what color to output
remoteViews.SetTextViewText(Resource.Id.command_text, items[position]);
if (results[position].ToLower().Split(' ')[0] == "invalid")
remoteViews.SetTextColor(Resource.Id.results_text, Color.Red);
else
remoteViews.SetTextColor(Resource.Id.results_text, Color.ParseColor("#52db02"));
if (items[position].ToLower().Split(' ')[1] == "help" || items[position].ToLower().Split(' ')[1] == "dir" || results[position].ToLower().Split(' ')[0] == "flipping")
{
if(results[position].ToLower().Split(' ')[0] != "invalid")
remoteViews.SetTextColor(Resource.Id.results_text, Color.White);
}
remoteViews.SetTextViewText(Resource.Id.results_text, results[position]);
}
//If statement checking whether the position in the list is the last one
//Sets blinking animation to visible
if (position == items.Count - 1)
{
remoteViews.SetViewVisibility(Resource.Id.blinking_dot, ViewStates.Visible);
}
else
{
remoteViews.SetViewVisibility(Resource.Id.blinking_dot, ViewStates.Gone);
}
Intent fillInIntent = new Intent();
fillInIntent.PutExtra("command_text", items[position]);
remoteViews.SetOnClickFillInIntent(Resource.Id.listviewlayout, fillInIntent);
return remoteViews;
}
public RemoteViews LoadingView => null;
public int ViewTypeCount => 1;
public long GetItemId(int position)
{
return position;
}
public bool HasStableIds => true;
public void OnDataSetChanged()
{
// Update your data source if needed
}
}
}
RemoteViewsService.cs:
namespace CommAndroid
{
//Handles the creation of the List View Factory Class --> Function to return our ListViewFactory
//Declare as service in android manifest, name is pointer for location
[Service(Enabled = true, Exported = false, Permission = "android.permission.BIND_REMOTEVIEWS")]
public class WidgetRemoteViewService : RemoteViewsService
{
public override IRemoteViewsFactory OnGetViewFactory(Intent intent)
{
return new ListViewFactory(this);
}
}
}
My code is uploaded to a github repo. https://github.com/chrisz99/CommAndroid
Figured out a solution to making the WidgetRemoteViewService populate with individual list data. Instead of using static lists, I ended up using SharedPreferences to store my list data as the value, and the AppWidgetId as the Key.
I added a class called WidgetDataManager.cs
WidgetDataManager.cs
Adjustments to WidgetProvider.cs
WidgetProvider.cs
Adjustments to WidgetRemoteViewService
WidgetRemoteViewService.cs
Adjustments to ListViewFactory.cs
ListViewFactory.cs