Use PopupExtensions.ShowPopupAsync function in Custom Control in MAUI - maui

I created a custom control in MAUI that must work if user select with a click or tap, a Popup must show with some content, let's say for example a Calculator instead a Keyboard. I'm using CommunityToolkit.Maui. But the sentence
var popup = new PickerControl();
var result = await PopupExtensions.ShowPopupAsync<PickerControl>(this, popup);
throw me an error because this in inside the control and expects a Page, so need to know how handle the page or parent page in the same control. Picker control is the Popup with the content.
The code:
public partial class EntryCalculator : Frame
{
TapGestureRecognizer _tapGestureRecognizer;
public EntryCalculator()
{
InitializeComponent();
}
///Properties here
private void Initialize()
{
_tapGestureRecognizer = new TapGestureRecognizer();
}
private async static void IsDisplayPickerPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var controls = (EntryCalculator)bindable;
if (newValue != null)
{
if ((bool)newValue)
{
var popup = new PickerControl();
var response = PopupExtensions.ShowPopupAsync<PickerControl>(this, popup);
if (response != null && response is decimal)
{
controls.Value = (decimal)response;
}
}
}
}
///... other methods

At first, you can get the current page from the navigation stack:
If you use the shell:
Page currentpage = Shell.Current.Navigation.NavigationStack.LastOrDefault();
If you use the NavigationPage:
Page currentpage = Navigation.NavigationStack.LastOrDefault();
Or just only use:Page currentpage = App.Current.MainPage.Navigation.NavigationStack.LastOrDefault();. The App.Current.MainPage will be the Shell or the NavigationPage, it depends on what you used in your project.
In addition, you can get the current page from the custom control. Such as:
public static class ViewExtensions
{
/// <summary>
/// Gets the page to which an element belongs
/// </summary>
/// <returns>The page.</returns>
/// <param name="element">Element.</param>
public static Page GetParentPage (this VisualElement element)
{
if (element != null) {
var parent = element.Parent;
while (parent != null) {
if (parent is Page) {
return parent as Page;
}
parent = parent.Parent;
}
}
return null;
}
}

Related

Button goes null when trying to access it

As the title suggests, my button (closeImage) goes null when I try to access it.
I have a canvas that pops up an image based on a tapped marker (button component) on a map. The said canvas will show the image together with the button in question. Clicking on the button closes the pop-up;
The button in question
However, once I press on the marker on the map to show the pop-up image, it gives me an error that the Close image is null. The close button does show on the first time I press on the marker on the map and able to close the pop-up, even with the error message. But once I click on the same marker on the map again, the image shows but the close button is not showing anymore.
Here's my block of code:
public class TappedMarkerManager : MonoBehaviour {
private static TappedMarkerManager instance;
private List<GameObject> markerImageList = new List<GameObject>();
string tappedMarkerName;
Button closeImage;
GameObject closeImageGO;
public static TappedMarkerManager Instance {
get {
if (instance == null) {
instance = FindObjectOfType<TappedMarkerManager>();
}
return instance;
}
}
void Start() {
closeImageGO = GameObject.FindGameObjectWithTag("close_img");
if(closeImageGO != null) {
closeImage = closeImageGO.GetComponent<Button>();
closeImage.onClick.AddListener(TappedMarkerInformation);
} else {
Debug.Log("closeImageGO is null");
}
//Loop through preview images.
//This will dynamically add the images on the list if more markers will be added on the map
//Use tag "marker_img"; must be same name with the marker and image
var markerImages = GameObject.FindGameObjectsWithTag("marker_img");
foreach(var markerImage in markerImages) {
TappedMarkerManager.Instance.markerImageList.Add(markerImage.gameObject);
//Set false to hide it at first
markerImage.SetActive(false);
}
}
public void TappedMarkerInformation() {
var name = tappedMarkerName;
foreach(var img in TappedMarkerManager.Instance.markerImageList) {
if(name == img.GetComponent<Image>().name) {
img.SetActive(true);
if(closeImage != null) {
if(!closeImage.gameObject.activeInHierarchy) {
closeImage.gameObject.SetActive(true);
}
} else {
Debug.LogError("Close image is null");
}
}
if(GetButtonName() == "ExitBtn") {
img.SetActive(false);
closeImage.gameObject.SetActive(false);
}
}
}
public void SetTappedName(string name) {
tappedMarkerName = name;
TappedMarkerInformation();
}
//Get the name of the button pressed
public string GetButtonName() {
if(EventSystem.current.currentSelectedGameObject != null) {
string ClickedButtonName = EventSystem.current.currentSelectedGameObject.name;
return ClickedButtonName;
} else {
return "";
}
}
}
The markers on the map are controlled from another script with:
...
TappedMarkerManager tappedMarkerManager;
tappedMarkerManager = new TappedMarkerManager();
...
private void HandleFingerTap(LeanFinger finger) {
Ray ray = Camera.main.ScreenPointToRay(finger.ScreenPosition);
if (Physics.Raycast(ray, out RaycastHit hit)) {
string markerName = hit.collider.gameObject.name;
tappedMarkerManager.SetTappedName(markerName);
}
}
Any help is highly appreciated. Thank you. I can provide the github repository if needed.
What I tried so far:
I already tried setting up the closeImage in the Inspector with the button
Made sure that the GameObject has the button component
Checked and verified on the hierarchy that the closeImage button is not being destroyed

Make focus go to next entry in a collection view

I have an application in .Net Maui that uses a collection view with an entry field and after the collection view one static entry field. If you are currently focused on the first entry in the collection view and hit tab or enter it will not navigate to the next entry in the collection view and focus on the static entry field. I need to find the best way to have the entry focus on the next entry in the collection view on complete.
I have tried changing the return type of the collection view entry field to Next and also tried the community toolkit SetFocusOnEntryCompletedBehavior function and both result in the same behavior of not navigating to the next entry from the collection view. Very similar to this issue that doesnt seem to be resolved. MAUI - CollectionView jump / focus to next entry
I found a workaround for you. You could try the following code:
Step1 Create a custom control , let's call it MyEntry (MyEntry.cs) which subclass Entry:
In this control we attach a BindableProperty IsExpectedToFocusProperty which we used it to judge whether it is goning to be focused. We also registered a new method OnIsExpectedToFocus to detect propertyChanged for our control. For info about BindableProperty, you could refer to Bindable properties.
MyEntry.cs,
public class MyEntry : Entry
{
public static readonly BindableProperty IsExpectedToFocusProperty = BindableProperty.Create("IsExpectedToFocus", typeof(bool), typeof(MyEntry), false, propertyChanged:OnIsExpectedToFocus);
public bool IsExpectedToFocus
{
get => (bool)GetValue(IsExpectedToFocusProperty);
set => SetValue(IsExpectedToFocusProperty, value);
}
static void OnIsExpectedToFocus(BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
if ((bool)newValue == true)
{
(bindable as Entry).Focus();
}
}
}
Step2 Consume custom control in CollectionView. We define the ReturnCommand and its parameter. we will bind them in the MainPageViewModel.
MainPage.xaml,
<CollectionView x:Name="mycoll" ItemsSource="{Binding ItemCollection}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<local:MyEntry x:Name="myentry" Focused="myentry_Focused"
IsExpectedToFocus="{Binding IsExpectedToFocus}"
Text="{Binding Title,Mode=TwoWay}" TextColor="Black"
ReturnCommand="{Binding Source={RelativeSource AncestorType={x:Type local:MainPageViewModel}}, Path=ReturnCommand}"
ReturnCommandParameter="{Binding .}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
In .cs file:
void myentry_Focused(System.Object sender, Microsoft.Maui.Controls.FocusEventArgs e)
{
var entry = sender as Entry;
foreach (var item in viewModel.ItemCollection)
{
if (entry.BindingContext != item)
{
item.IsExpectedToFocus = false;
}
}
}
Step3 Design our MainPageViewModel. I define an ObservableCollection which ItemSource will bind to. And add three items just for test.
Then I think the most important part is to design the Command. Let me explain it briefly. When we press the entry of an Entry, we fire the ReturnCommand and get current Item through ReturnCommandParameter. We get the index of current Item in ItemCollection. So the next entry which needs to be focused corresponds to the index+1 Item. Then we changed the IsExpectedToFocus of the next entry and fire the OnIsExpectedToFocus method which set the entry be focused. Done!
MainPageViewModel.cs
public class MainPageViewModel
{
public ObservableCollection<Item> ItemCollection { get; set; } = new ObservableCollection<Item>();
public Command ReturnCommand
{
get
{
return new Command<Item>((e) =>
{
e.IsExpectedToFocus = false;
int index = ItemCollection.IndexOf(e); // get the current index
if (index != -1)
{
int nextIndex;
// if last entry, next index is 0, else index +1
if (index < (ItemCollection.Count() - 1))
{
nextIndex = index + 1;
ItemCollection[nextIndex].IsExpectedToFocus = true;
}
else if(index == (ItemCollection.Count() - 1))
{
nextIndex = 0;
ItemCollection[nextIndex].IsExpectedToFocus = true;
}
}
});
}
}
public MainPageViewModel()
{
//add three item for test
ItemCollection.Add(
new Item
{
Title = "12345",
IsExpectedToFocus = false
}) ;
ItemCollection.Add(
new Item
{
Title = "23456",
IsExpectedToFocus = false
});
ItemCollection.Add(
new Item
{
Title = "34567",
IsExpectedToFocus = false
});
}
}
Also, this is Item.cs, should implement INotifyPropertyChanged
public class Item : INotifyPropertyChanged
{
public string title;
public bool isExpectedToFocus;
public string Title
{
get
{
return title;
}
set
{
title = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
}
}
public bool IsExpectedToFocus
{
get
{
return isExpectedToFocus;
}
set
{
isExpectedToFocus = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsExpectedToFocus)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Hope it works for you.

Create WinUI3/MVVM Most Recently Used (MRU) List in Menu Bar

I would like to create a classic "Recent Files" list in my Windows app menu bar (similar to Visual Studio's menu bar -> File -> Recent Files -> see recent files list)
The MRU list (List < string > myMRUList...) is known and is not in focus of this question. The problem is how to display and bind/interact with the list according to the MVVM rules.
Microsoft.Toolkit.Uwp.UI.Controls's Menu class will be removed in a future release and they recommend to use MenuBar control from the WinUI. I haven't found any examples, that use WinUI's MenuBar to create a "Recent Files" list.
I'm using Template Studio to create a WinUI 3 app. In the ShellPage.xaml I added
<MenuFlyoutSubItem x:Name="mruFlyout" Text="Recent Files"></MenuFlyoutSubItem>
and in ShellPage.xaml.c
private void Button_Click(object sender, RoutedEventArgs e)
{
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test1_" + DateTime.Now.ToString("MMMM dd") } );
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test2_" + DateTime.Now.ToString("MMMM dd") } );
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test3_" + DateTime.Now.ToString("MMMM dd") } );
}
knowing this is not MVVM, but even this approach does not work properly, because the dynamically generated MenuFlyoutItem can be updated only once by Button_Click() event.
Could anybody give me an example, how to create the "Recent Files" functionality, but any help would be great! Thanks
Unfortunately, it seems that there is no better solution than handling this in code behind since the Items collection is readonly and also doesn't response to changes in the UI Layout.
In addition to that, note that because of https://github.com/microsoft/microsoft-ui-xaml/issues/7797, updating the Items collection does not get reflected until the Flyout has been closed and reopened.
So assuming your ViewModel has an ObservableCollection, I would probably do this:
// 1. Register collection changed
MyViewModel.RecentFiles.CollectionChanged += RecentFilesChanged;
// 2. Handle collection change
private void RecentFilesChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// 3. Create new UI collection
var flyoutItems = list.Select(entry =>
new MenuFlyoutItem()
{
Text = entry.Name
}
);
// 4. Updating your MenuFlyoutItem
mruFlyout.Items.Clear();
flyoutItems.ForEach(entry => mruFlyout.Items.Add(entry));
}
Based on chingucoding's answer I got to the "recent files list" binding working.
For completeness I post the detailed code snippets here (keep in mind, that I'm not an expert):
Again using Template Studio to create a WinUI 3 app.
ShellViewModel.cs
// constructor
public ShellViewModel(INavigationService navigationService, ILocalSettingsService localSettingsService)
{
...
MRUUpdateItems();
}
ShellViewModel_RecentFiles.cs ( <-- partial class )
using System.Collections.ObjectModel;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
namespace App_MostRecentUsedTest.ViewModels;
public partial class ShellViewModel : ObservableRecipient
{
public ObservableCollection<MRUItem> MRUItems{ get; set;} = new();
// update ObservableCollection<MRUItem>MRUItems from MostRecentlyUsedList
public void MRUUpdateItems()
{
var mruTokenList = StorageApplicationPermissions.MostRecentlyUsedList.Entries.Select(entry => entry.Token).ToList();
var mruMetadataList = StorageApplicationPermissions.MostRecentlyUsedList.Entries.Select(entry => entry.Metadata).ToList(); // contains path as string
MRUItems.Clear(); var i = 0;
foreach (var path in mruMetadataList)
{
MRUItems.Add(new MRUItem() { Path = path, Token = mruTokenList[i++] });
}
}
// called if user selects a recent used file from menu bar list
[RelayCommand]
protected async Task MRULoadFileClicked(int? fileId)
{
if (fileId is not null)
{
var mruItem = MRUItems[(int)fileId];
FileInfo fInfo = new FileInfo(mruItem.Path ?? "");
if (fInfo.Exists)
{
StorageFile? file = await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(mruItem.Token);
if (file is not null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path); // store file.Path into Metadata
MRUUpdateItems();
// LOAD_FILE(file);
}
}
else
{
}
}
await Task.CompletedTask;
}
[RelayCommand]
protected async Task MenuLoadFileClicked()
{
StorageFile? file = await GetFilePathAsync();
if (file is not null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path); // store file.Path into Metadata
MRUUpdateItems();
// LOAD_FILE(file);
}
await Task.CompletedTask;
}
// get file path with filePicker
private async Task<StorageFile?> GetFilePathAsync()
{
FileOpenPicker filePicker = new();
filePicker.FileTypeFilter.Add(".txt");
IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
WinRT.Interop.InitializeWithWindow.Initialize(filePicker, hwnd);
return await filePicker.PickSingleFileAsync();
}
public class MRUItem : INotifyPropertyChanged
{
private string? path;
private string? token;
public string? Path
{
get => path;
set
{
path = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(path));
}
}
public string? Token
{
get => token;
set => token = value;
}
public event PropertyChangedEventHandler? PropertyChanged;
}
}
ShellPage.xaml
<MenuBar>
<MenuBarItem x:Name="ShellMenuBarItem_File">
<MenuFlyoutItem x:Uid="ShellMenuItem_File_Load" Command="{x:Bind ViewModel.MenuLoadFileClickedCommand}" />
<MenuFlyoutSubItem x:Name="MRUFlyout" Text="Recent Files..." />
</MenuBarItem>
</MenuBar>
ShellPage.xaml.cs
// constructor
public ShellPage(ShellViewModel viewModel)
{
...
// MRU initialziation
// assign RecentFilesChanged() to CollectionChanged-event
ViewModel.MRUItems.CollectionChanged += RecentFilesChanged;
// Add (and RemoveAt) trigger RecentFilesChanged-event to update MenuFlyoutItems
ViewModel.MRUItems.Add(new MRUItem() { Path = "", Token = ""});
ViewModel.MRUItems.RemoveAt(ViewModel.MRUItems.Count - 1);
}
// MRU Handle collection change
private void RecentFilesChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// project each MRUItems list element into a new UI MenuFlyoutItem flyoutItems list
var i = 0;
var flyoutItems = ViewModel.MRUItems.Select(entry =>
new MenuFlyoutItem()
{
Text = " " + i.ToString() + " " + FilenameHelper.EllipsisString(entry.Path, 65),
Command = ViewModel.MRULoadFileClickedCommand,
CommandParameter = i++
}
);
//// If you want to update the list while it is shown,
//// you will need to create a new FlyoutItem because of
//// https://github.com/microsoft/microsoft-ui-xaml/issues/7797
// Create a new flyout and populate it
var newFlyout = new MenuFlyoutSubItem();
newFlyout.Text = MRUFlyout.Text; // Text="Recent Files...";
// Updating your MenuFlyoutItem
flyoutItems.ToList().ForEach(item => newFlyout.Items.Add(item));
// Get index of old sub item and remove it
var oldIndex = ShellMenuBarItem_File.Items.IndexOf(MRUFlyout);
ShellMenuBarItem_File.Items.Remove(MRUFlyout);
// Insert the new flyout at the correct position
ShellMenuBarItem_File.Items.Insert(oldIndex, newFlyout);
// Assign newFlyout to "old"-MRUFlyout
MRUFlyout = newFlyout;
}

Set ProgressBar visibility from ViewModel before and after retrieving data from webservice service

I am creating a Windows Phone 8.1 app using an MVVM design pattern and the MVVM Light toolkit.
I am trying to create a simple login page that takes a username and password from a user and authenticates with a webservice. During this authentication process I want to show a ProgressBar (loading dots) at the top of the page to indicate that something is happening.
I have successfully created my ViewModel and bound my View to allow me to control the visibility of the ProgressBar from a Command attached to my login button. This works, however, the UI only updates to show the ProgressBar once the entire Login process is completed, making the use of a progress indicator pointless.
How do I first set the visibility of the ProgressBar (and have the UI update) then go off an perform my Login process?
Here is some code:
xaml
<ProgressBar IsIndeterminate="True" Height="1" Visibility="{Binding Login.ProgressVisibility, UpdateSourceTrigger=PropertyChanged, FallbackValue=Collapsed}" />
ViewModel
public class LoginViewModel : ViewModelBase
{
private IDialogService dialogService;
private INavigationService navigationService;
private string _username;
private string _password;
private Visibility _progressVisibility = Visibility.Collapsed;
/// <summary>
/// User login name / email address
/// </summary>
public string Username
{
get
{
return _username;
}
set
{
Set(() => Username, ref _username, value);
}
}
/// <summary>
/// User password
/// </summary>
public string Password
{
get
{
return _password;
}
set
{
Set(() => Password, ref _password, value);
}
}
/// <summary>
/// Is loading in progress
/// </summary>
public Visibility ProgressVisibility
{
get
{
return _progressVisibility;
}
set
{
Set(() => ProgressVisibility, ref _progressVisibility, value);
}
}
/// <summary>
/// Perform login action
/// </summary>
public RelayCommand LoginCommand { get; private set; }
/// <summary>
/// constructor
/// </summary>
/// <param name="dialogService"></param>
public LoginViewModel(IDialogService dialogService, INavigationService navigationService)
{
this.LoginCommand = new RelayCommand(this.Login, CanLogin);
this.dialogService = dialogService;
this.navigationService = navigationService;
}
public void Login()
{
this.ProgressVisibility = Visibility.Visible;
//check the credentials have been provided
if (this._username == null || this._password == null)
{
//show dialogue
dialogService.ShowMessage("Please enter you login credentials", "Login");
}
else
{
//perform an authentication request with the service
ValidateLoginRequest request = new ValidateLoginRequest();
request.Device = new Device();
request.UserName = this.Username;
request.Password = this.Password;
var response = Task.Run(() => LoginAsync(request)).GetAwaiter().GetResult();
if (response != null)
{
if (response.IsSuccessful)
{
navigationService.NavigateTo("Main");
}
else
{
dialogService.ShowMessage(response.ErrorCode + " :" + response.ErrorMessage, "Login");
}
}
else
{
//failure
dialogService.ShowMessage("ECOM000: An unknown error has occurred", "Login");
}
}
}
async Task<ValidateLoginResponse> LoginAsync(ValidateLoginRequest request)
{
Model.RuntimeDataService proxy = new Model.RuntimeDataService();
ValidateLoginResponse response = (ValidateLoginResponse)await proxy.PostAsync(request, typeof(ValidateLoginResponse), "ValidateLogin");
return response;
}
public bool CanLogin()
{
return true;
}
}
I solved this using Kotlin by adding my own CustomBindingAdapter:
#BindingAdapter("android:visibility")
fun setVisibility(view: View, visible: Boolean) {
view.visibility = if (visible) View.INVISIBLE else View.VISIBLE
}
Inside your view.xml:
First add data variable:
<data>
<variable name="loginViewModel" type="yourpackage.viewmodel.LoginViewModel"/>
</data>
<ProgressBar
android:id="#+id/login_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="#{!loginViewModel.loginProgress}" />
Your Activity must have:
val binding = DataBindingUtil.setContentView<LoginViewBinding>(this, R.layout.login_view)
val loginViewModel = LoginViewModel(this)
binding.setLoginViewModel(loginViewModel)
At the end your ViewModel needs to handle the visibility:
var loginProgress = ObservableField<Boolean>()
fun doSomething(){
this.loginProgress.set(true)
}
And set loginProgress.set(false) wherever you need to stop the progressBar.
I hope this can help someone else ;)
You are creating a background Task and then causing the UI thread to wait for the response.
Try awaiting the Task returned from the LoginAsync method:
var response = await LoginAsync(request);
This should allow your UI thread to continue, with the rest of the method acting as a callback.
You will probably need to add the async keyword to the Login method.

How can you create a widget inside an android application? (Use App Widget Host class?)

I need to create an application that contains multiple widgets. These are not desktop widgets. I need to be able to interact with these widgets as if they were desktop widgets, but they need to be encased inside a larger application. (Each widget has it's own functionality and behavior when clicked.)
Is this possible in android? Or do I need to create an application and create each object that I'd like to behave like a widget actually as a view?
Ex. The parent app is for a car. Example of "in app" widgets are: oil change history (list of last three oil change dates visible, clicking on a date will open a scan of the receipt, etc.), tire pressure monitor, lap speed history (shows last four laps, pinching and expanding will show more than four), etc.
Can I make each of these objects widgets? Or do they have to be views inside the app?
Edit: The Android developer's App Widget Host page mentions: "The AppWidgetHost provides the interaction with the AppWidget service for apps, like the home screen, that want to embed app widgets in their UI."
Has anyone created their own App Widget Host or worked directly with this class?
Create your appwidget normally
Then in your activity add this code
Call selectWidget() to open popup to pick avaible widget
//init
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new AppWidgetHost(this, R.id.APPWIDGET_HOST_ID);
//select widget
void selectWidget() {
int appWidgetId = this.mAppWidgetHost.allocateAppWidgetId();
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
addEmptyData(pickIntent);
startActivityForResult(pickIntent, R.id.REQUEST_PICK_APPWIDGET);
}
void addEmptyData(Intent pickIntent) {
ArrayList customInfo = new ArrayList();
pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo);
ArrayList customExtras = new ArrayList();
pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras);
};
//Configure the widget
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK ) {
if (requestCode == REQUEST_PICK_APPWIDGET) {
configureWidget(data);
}
else if (requestCode == REQUEST_CREATE_APPWIDGET) {
createWidget(data);
}
}
else if (resultCode == RESULT_CANCELED && data != null) {
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (appWidgetId != -1) {
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
}
}
}
private void configureWidget(Intent data) {
Bundle extras = data.getExtras();
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (appWidgetInfo.configure != null) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.setComponent(appWidgetInfo.configure);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
} else {
createWidget(data);
}
}
//adding it to you view
public void createWidget(Intent data) {
Bundle extras = data.getExtras();
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
hostView.setAppWidget(appWidgetId, appWidgetInfo);
layout.addView(hostView);
}
//Update widget
#Override
protected void onStart() {
super.onStart();
mAppWidgetHost.startListening();
}
#Override
protected void onStop() {
super.onStop();
mAppWidgetHost.stopListening();
}
//Now to remove it call this
public void removeWidget(AppWidgetHostView hostView) {
mAppWidgetHost.deleteAppWidgetId(hostView.getAppWidgetId());
layout.removeView(hostView);
}
Hope this helps
If you want to embed a specific widget in your app, and know the package name and class name:
public boolean createWidget(View view, String packageName, String className) {
// Get the list of installed widgets
AppWidgetProviderInfo newAppWidgetProviderInfo = null;
List<AppWidgetProviderInfo> appWidgetInfos;
appWidgetInfos = mAppWidgetManager.getInstalledProviders();
boolean widgetIsFound = false;
for(int j = 0; j < appWidgetInfos.size(); j++)
{
if (appWidgetInfos.get(j).provider.getPackageName().equals(packageName) && appWidgetInfos.get(j).provider.getClassName().equals(className))
{
// Get the full info of the required widget
newAppWidgetProviderInfo = appWidgetInfos.get(j);
widgetIsFound = true;
break;
}
}
if (!widgetIsFound) {
return false;
} else {
// Create Widget
int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
AppWidgetHostView hostView = mAppWidgetHost.createView(getApplicationContext(), appWidgetId, newAppWidgetProviderInfo);
hostView.setAppWidget(appWidgetId, newAppWidgetProviderInfo);
// Add it to your layout
LinearLayout widgetLayout = view.findViewById(R.id.widget_view);
widgetLayout.addView(hostView);
// And bind widget IDs to make them actually work
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
boolean allowed = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, newAppWidgetProviderInfo.provider);
if (!allowed) {
// Request permission - https://stackoverflow.com/a/44351320/1816603
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, newAppWidgetProviderInfo.provider);
final int REQUEST_BIND_WIDGET = 1987;
startActivityForResult(intent, REQUEST_BIND_WIDGET);
}
}
return true;
}
}