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

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;
}
}

Related

Public List In MainActivity and MVVM

I need some help with MVVM architecture.
I have a RecyclerView that receives LiveData and display it perfectly, however, my recyclerView requires another source of Data to customize colors and backgrounds of TextViews. for now I'm using a public list declared in the Mainactivity, But I've read that it's not a good practice.
is it possible to perform a non-live request to database from inside RecyclerView, in order to replace the public list ? if not I would really like some suggestions.
here is my onBindViewHolder:
#Override
public void onBindViewHolder(#NonNull ResultRecyclerViewAdapter.ResultHolder holder, int position) {
Results currentResult = results.get(position);
holder.ston1.setText(currentResult.getSton1());
holder.ston2.setText(String.valueOf(currentResult.getSton2()));
holder.ston1.setBackgroundColor(0xFF12FF45);
holder.ston2.setBackgroundColor(0xFF12FF45);
holder.ston1.getBackground().setAlpha(100);
holder.ston2.getBackground().setAlpha(100);
for (Ston ston: MainActivity.Stons){
if (currentResult.getStonCode().equals(ston.getStonCode()) && currentResult.getStonType().equals(ston.getStonType())){
switch (ston.getStonSelected()) {
case "WADS":
holder.ston1.getBackground().setAlpha(255);
break;
case "WQAS":
holder.ston2.getBackground().setAlpha(255);
break;
}
break;
}
}
holder.ston1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Boolean found = false;
for (Ston ston: MainActivity.Stons){
if (currentResult.getStonCode().equals(ston.getStonCode())){
found = true;
break;
}
}
if (!found) {
holder.ston1.getBackground().setAlpha(255);
MainActivity.Stons.add(new Stons(currentResult.getStonCode(),"WADS",
currentResult.getStonType()));
}
else {
for (Ston ston : MainActivity.Stons) {
if (currentResult.getStonCode().equals(ston.getStonCode()) && ston.getStonSelected().equals("WADS") &&
ston.getStonType().equals(currentResult.getStonType())){
MainActivity.Stons.remove(ston);
holder.ston1.getBackground().setAlpha(100);
break;
}
}
}
}
});
holder.ston2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Boolean found = false;
for (Ston ston: MainActivity.Stons){
if (currentResult.getStonCode().equals(ston.getStonCode())){
found = true;
break;
}
}
if (!found) {
holder.ston2.getBackground().setAlpha(255);
MainActivity.Stons.add(new Stons(currentResult.getStonCode(),"WQAS",
currentResult.getStonType()));
}
else {
for (Ston ston : MainActivity.Stons) {
if (currentResult.getStonCode().equals(ston.getStonCode()) && ston.getStonSelected().equals("WQAS") &&
ston.getStonType().equals(currentResult.getStonType())){
MainActivity.Stons.remove(ston);
holder.ston2.getBackground().setAlpha(100);
break;
}
}
}
}
});
One option that I see is to create new type specifically for your recyclerview adapter that will hold both Results object and information that you use for background alpha. So in your activity (or fragment) when livedata observer is triggered you don't directly pass it to adapter, but first create collection of objects of your new type, and then pass it to adapter. And I strongly suggest you to use Kotlin if possible, there you can use collection mapping to map collection from the db to your new type's collection.

Update ListView via AsyncTask or IntentService

I am trying to Update my Custom ListView which is fed by two String Arrays:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getStringArray(ARG_PARAM1);
mParam2 = getArguments().getStringArray(ARG_PARAM2);
}
setupListView();
}
private void setupListView() {
listItemList = new ArrayList();
if (mParam1 != null && mParam2 != null && mParam1.length == mParam2.length) {
for (int i = 0; i < mParam1.length; i++) {
listItemList.add(new MyListItem(mParam1[i], (mParam2[i]).substring(0, 75) + "..."));
}
} else {
listItemList.add(new MyListItem("Loading...", "Swipe Down for Update"));
}
mAdapter = new MyListAdapter(getActivity(), listItemList);
}
mParam1 and mParam2 are Values which are fetched by an XML parser (IntentService) class in the MainActivity which i can show if needed.
Now, if i am to fast, and the mPara1 and mPara2 is empty there won´t be any ListView shown. Now i want to solve this by some AsyncTask or IntentService whatever is useful. I tried AsyncTask, which didn´t work at all. I tried notifyDataSetChanged() which didn´t work too...
Now, how could i solve this....
Using AsyncTask i have the problem that i don´t know how to passt the two Arrays to publishProgress() correctly
THis is how my AsyncTask looks like:
class UpdateListView extends AsyncTask<Void, String, Void> {
private MyListAdapter adapter;
private ArrayList listItemList;
#Override
protected void onPreExecute() {
adapter = (MyListAdapter) mListView.getAdapter();
}
#Override
protected Void doInBackground(Void... params) {
for (String item1 : mParam1) {
publishProgress(item1);
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
adapter.add(new MyListItem(values[0], values[1]));
}
#Override
protected void onPostExecute(Void result) {
Log.d("onPostExecute", "Added successfully");
}
}
Okay solved it...My Fragments are running in same Activity where the Data is loaded in, so i just created getter and setter in MainActivity and access them in the needed Fragment via
String[] titles =(MainActivity) getActivity()).getTitlesArray();
String[] text=(MainActivity) getActivity()).getTextArray();
Whatever i do trying setting Bundle with
bundle.putStringArray(TITLES,titles);
doesn´t work. Should work using parceable/serializable class but didn´t try...

Android WebView in a ViewSwitcher loadUrl loads once

I have in my ViewSwitcher a ListView and a WebView. In my ListView's adapter, I have an onclick listener that writes the clicked url in the list to sharedpreferences. I'm trying to load that url into the WebView using an onSharedPreferencesChangedListener.
This is the code in my adapter:
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewSwitcher.showNext();
Settings.writeSettings(context, "webviewUrl",
urls.get(position));
}
});
return convertView;
And in the preference listener:
#Override
public void onSharedPreferenceChanged(SharedPreferences pref, String key) {
if (key.equals("webviewUrl")) {
Log.d("TAG", pref.getString(key, null));
WebView wv = (WebView) findViewById(R.id.rss_webview);
wv.getSettings().setJavaScriptEnabled(true);
wv.setWebViewClient(new MyWebViewClient());
wv.loadUrl("about:blank");
wv.loadUrl(pref.getString(key, null));
}
}
This works great except it only works once. The preference listener code logs the correct urls, and the code executes each time I want it to, but wv.loadUrl() method seems to do nothing after the first successful call. Can anyone explain to me why this is happening and perhaps offer a solution? Thanks.
I solved the problem by implementing a static ViewHolder on the WebView whose reference I needed to keep longer than its views' lifecycle.
private static final class WebViewHolder {
WebView wv;
}
#Override
public void onSharedPreferenceChanged(SharedPreferences pref, String key) {
WebViewHolder holder = new WebViewHolder();
if (key.equals("webviewUrl")) {
if (wv == null) {
wv = new WebView(this);
holder.wv = (WebView) findViewById(R.id.rss_webview);
holder.wv.getSettings().setJavaScriptEnabled(true);
holder.wv.setWebViewClient(new MyWebViewClient());
wv.setTag(holder);
} else {
holder = (WebViewHolder) wv.getTag();
}
holder.wv.loadUrl("about:blank");
holder.wv.loadUrl(pref.getString(key, null));
}
}

GWT Cell Tree - leaf nodes

I'm using a cell tree and I have this problem:
I get the data via RPC calls. I decide if a node is a leaf or not - based on the data that I get for its children. For example - if a node has a son called "foo" - then this node should be a leaf.
I don't know how to make this node to be a leaf and not to show its children on the tree. (instead, I want to show them somewhere else, when clicking on the node)
Is it possible? Does anyone have an idea?
Please help me, I'm stuck with it for 2 days...
Thanks!
You can override isLeaf() method to return true or false.
There will be a problem, however, from the UI perspective. Before a user clicks on a node, you don't know if this should be a node or a leaf. This is a little confusing, although I saw such implementations more than once. If your tree is not very large, consider loading all data at once, and then building it the way you want - creating nodes or leafs as necessary.
If each node has a type, could you create some list or map of types that aren't expected to have children in your TreeViewModel impl?
In an impl I did I used a meta-model for all types, but it's not a requirement.
E.g.,
#Override
public boolean isLeaf(Object value) {
boolean result = true;
if (value == null) {
result = false; // assumes all root nodes have children
} else if (value instanceof NavNode) {
final NavNode currentNode = (NavNode) value;
final NodeType currentNodeType = NodeType.fromValue(currentNode.getType());
if (currentNode.hasChildren() || NodeHelper.couldHaveChildren(currentNodeType)) {
result = false;
}
}
return result;
}
// Create a data provider for root nodes
protected ListDataProvider<NavNode> getDataProvider(Collection<NavNode> rootNodes) {
return new ListDataProvider<NavNode>(new LinkedList<NavNode>(rootNodes));
}
// Create a data provider that contains the immediate descendants.
protected AsyncDataProvider<NavNode> getDataProvider(final NavNode node) {
return new AsyncDataProvider<NavNode>() {
#Override
protected void onRangeChanged(final HasData<NavNode> display) {
final Set<NavNode> clientNodes = util.getAncestorNodes(node);
clientNodes.add(node);
final NavigationInfo clientInfo = new NavigationInfo(clientNodes);
navigationService.getNavInfo(clientInfo, node, resources, qualifications, new SafeOperationCallback<NavigationInfo>(eventBus, false) {
#Override
public void onFailureImpl(Throwable caught) {
GWT.log("Something went wrong retreiving children for " + node.getName(), caught);
updateRowCount(0, false);
}
#Override
public void onSuccessImpl(OperationResult<NavigationInfo> or) {
util.mergeNavInfo(or.getResult());
final NavNode nodeFromServer = util.getNode(node.getId());
final Range range = display.getVisibleRange();
final int start = range.getStart();
final Set<NavNode> nodes = util.getNodes(nodeFromServer.getChildren());
updateRowData(display, start, new LinkedList<NavNode>(nodes));
}
});
}
};
}
private static class NodeHelper {
private static final Set<NodeType> PARENTAL_NODE_TYPES;
static {
PARENTAL_NODE_TYPES = new HashSet<NodeType>();
PARENTAL_NODE_TYPES.add(NodeType.ASSET_OWNER);
PARENTAL_NODE_TYPES.add(NodeType.OPERATING_DAY);
PARENTAL_NODE_TYPES.add(NodeType.RESOURCES);
PARENTAL_NODE_TYPES.add(NodeType.RESOURCE);
PARENTAL_NODE_TYPES.add(NodeType.ENERGY);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE);
PARENTAL_NODE_TYPES.add(NodeType.DAY_AHEAD_CLEARED_OFFERS);
PARENTAL_NODE_TYPES.add(NodeType.DRR_LOAD_FORCAST);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE_DISPATCH);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE_RAMP_RATE);
}
public static boolean couldHaveChildren(NodeType nodeType) {
boolean result = false;
if (PARENTAL_NODE_TYPES.contains(nodeType)) {
result = true;
}
return result;
}
}
}

Plugin dev: How to search only for interfaces in DLTK/PDT plugin?

I develop extension of PDT plugin. I need dialog with interfaces only (not classes). Basic code looks like it:
OpenTypeSelectionDialog2 dialog = new OpenTypeSelectionDialog2(
DLTKUIPlugin.getActiveWorkbenchShell(),
multi,
PlatformUI.getWorkbench().getProgressService(),
null,
type,
PHPUILanguageToolkit.getInstance());
It's works fine but I get classes and interfaces together (type variables). Is any method to filter it? I can't find this kind of mechanism in PDT but classes and interfaces are recognize correctly (icons next to names).
I don't know if its the best solution but it works.
int falseFlags = 0;
int trueFlags = 0;
IDLTKSearchScope scope = SearchEngine.createSearchScope(getScriptFolder().getScriptProject());
trueFlags = PHPFlags.AccInterface;
OpenTypeSelectionDialog2 dialog = new OpenTypeSelectionDialog2(
DLTKUIPlugin.getActiveWorkbenchShell(),
multi,
PlatformUI.getWorkbench().getProgressService(),
scope,
IDLTKSearchConstants.TYPE,
new PHPTypeSelectionExtension(trueFlags, falseFlags),
PHPUILanguageToolkit.getInstance());
And PHPTypeSelectionExtension looks like this:
public class PHPTypeSelectionExtension extends TypeSelectionExtension {
/**
* #see PHPFlags
*/
private int trueFlags = 0;
private int falseFlags = 0;
public PHPTypeSelectionExtension() {
}
public PHPTypeSelectionExtension(int trueFlags, int falseFlags) {
super();
this.trueFlags = trueFlags;
this.falseFlags = falseFlags;
}
#Override
public ITypeInfoFilterExtension getFilterExtension() {
return new ITypeInfoFilterExtension() {
#Override
public boolean select(ITypeInfoRequestor typeInfoRequestor) {
if (falseFlags != 0 && (falseFlags & typeInfoRequestor.getModifiers()) != 0) {
// Try to filter by black list.
return false;
} else if (trueFlags == 0 || (trueFlags & typeInfoRequestor.getModifiers()) != 0) {
// Try to filter by white list, if trueFlags == 0 this is fine 'couse we pass black list.
return true;
} else {
// Rest is filter out.
return false;
}
}
};
}
#SuppressWarnings("restriction")
#Override
public ISelectionStatusValidator getSelectionValidator() {
return new TypedElementSelectionValidator(new Class[] {IType.class, INamespace.class}, false);
}
}