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.
Related
I am bootstrapping the IEventBroker in a compat-layer Eclipse RCP app.
I have two views: Triggerer and Receiver.
Triggerer (excerpts):
private IEventBroker eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class);
btn.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
IStructuredSelection selection = viewer.getStructuredSelection();
List selectionList = selection.toList();
for (Object s : selectionList) {
if (s instanceof MyObject) {
matches.add(s);
}
}
eventBroker.send(MyEventConstants.TOPIC_OBJECT_CHANGED, matches);
}
}
Receiver (excerpts):
#Override
public void handleEvent(Event event) {
Object data = event.getProperty(EVENT_DATA);
switch (event.getTopic()) {
case MyEventConstants.TOPIC_OBJECT_CHANGED:
try {
if (data instanceof ArrayList) {
List<MyObject> matches = null;
try {
matches = (List<MyObject>) data;
}
catch (ClassCastException e) {
}
Subthing sub = buildSubthing(matches);
getContentViewer().getContents()
.setAll(Collections.singletonList(sub));
}
}
break;
}
}
buildSubthing does stuff with the respective received data, and sets it to the contents of a GEF4 editor.
In some cases this works just fine, in some it doesn't.
handleEvent() is triggered more than once, although the event hashCode is always the same, and I don't understand why. The topic is the same and the data is also the same. However, buildSubthing just stalls for no apprent reason with some data while it doesn't for other. The data is structurally the same in both cases.
How can I control how often handleEvent is called, as I think the number of times it's called is the reason while the Subthing is sometimes not correctly constructed?
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;
}
}
I can't understand that part, neither trying the showcase examples.
I'm using an extension of AsyncDataProvider to bind my tree to RPC service. Here's my method:
public <T> NodeInfo<?> getNodeInfo(T value) {
/*
if (value instanceof Categoria) {
dataProvider.setCurrentParent((Categoria)value);
}
*/
return new DefaultNodeInfo<Categoria>(dataProvider, new CategoriaCell());
}
"currentParent" is my stuff: except for (null => root) values, I set the parent to pass via RPC to my service. Actually, in my widget code:
dataProvider = new CategorieTreeDataProvider() {
#Override
protected void onRangeChanged(HasData<Categoria> display) {
updateTree(getCurrentParent());
}
};
private void updateTree(Categoria categoria) {
rpcService.getCategorie(categoria, new AsyncCallback<Categoria[]>() {
#Override
public void onSuccess(Categoria[] result) {
dataProvider.updateRowCount(result.length, true);
dataProvider.updateRowData(0, Arrays.asList(result));
}
#Override
public void onFailure(Throwable caught) {
Window.alert(caught.toString());
}
});
}
My rpc-server code, however, is working as expected:
#Override
public Categoria[] getCategorie(Categoria parent) {
List<Categoria> categoryList = categorieDao.listByProperty("parent", parent);
for (Categoria c : categoryList) {
if (categorieDao.listByProperty("parent", c).size() == 0) {
c.setLeaf(true);
}
}
return categoryList.toArray(new Categoria[0]);
}
**Then I add some data to my Categories: 'GrandFather', 'Father' and 'Son'.
Unfortunately, after loading my widget, I see:
The grandfather correctly, with his "+" how expected;
Then I click it and...
The grandfather disappear and I see 'Father' with his '+'
same for father -> son
I suspect the bug is in updateRowCount / updateRowData usage.**
Any ideas?
The getNodeInfo is called whenever you open a node so you have to create distinct DataProvider for each of the nodes's childs.
public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) {
return new DefaultNodeInfo<Category>(dataProvider, new CategoriaCell());
}
else if (value instanceof Categoria) {
Category category = (Category)value;
return new DefaultNodeInfo<Grandfather>(new ListDataProvider<Grandfather>(category.getGrandFathers()),new GrandFatherCell());
}
else if (value instanceof Grandfather) {
Grandfather grandfather = (Grandfather)value;
return new DefaultNodeInfo<Father>(new ListDataProvider<Father>(granfather.getFathers()),new FatherCell());
}
else if (value instanceof Father) {
//same as above but with fathers.
}
}
The category.getGrandFathers() function can for example do a RPC request to the server or just return the list if you retrieve everything in one RPC request.
UPDATE based on comment:
So in case you have only one class and want to achieve a dynamic CellTree (number of levels are not pre-determined) you could take following approach.
public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) {
return new DefaultNodeInfo<Category>(dataProvider, new CategoriaCell());
}
else {
Category category = (Category)value;
return new DefaultNodeInfo<Category>(new ListDataProvider<Category>(category.getSubCategories()),new CategoryCell());
}
}
category.getSubCategories() is either an RPC call which retrieves the subcategories for the current category or if the Category class is a linked list type datastructure it could just return the list of subcategories.
Each data provider updates a given "list" (child nodes of a given parent node), so you have to use a distinct data provider instance for each parent node, or your calls will update some random list.
has someone been able to correctly to update a cell browser at runtime, i.e. when u remove a node or add a node, the change is reflected immediately in the CEll Browser, because I am using a List and when i am making a change it is not being updated on the spot
You can use ListDataProvider setList(...) method for dynamic updates. Here is an example how I update cell browser via RPC:
private void loadAllData(final ListDataProvider<Data> dataProvider) {
dBservice.getAllData(new AsyncCallback<List<Data>>() {
public void onSuccess(List<Data> result) {
dataProvider.setList(result);
}
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
});
}
to refresh a cellBrowser you have to close all the child on the root node.
anyway something like this
for (int i = 0; i < cellBrowser.getRootTreeNode().getChildCount(); i++) {
cellBrowser.getRootTreeNode().setChildOpen(i, false);
}
the AsyncDataProvider calls refreshes data
private final class Model implements TreeViewModel{
private List<ZonaProxy> zonaList = null;
private List<CategoriaProxy> categoriaList = null;
public void setCategoriaList(List<CategoriaProxy> categoriaList) {
this.categoriaList = categoriaList;
}
public void setListZona(List<ZonaProxy> zonaList) {
this.zonaList = zonaList;
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public <T> NodeInfo<?> getNodeInfo(T value) {
CategoryDataProvider dataProvider1 = new CategoryDataProvider();
return new DefaultNodeInfo(dataProvider1, new CategoriaCell());
}
/**
* Check if the specified value represents a leaf node. Leaf nodes cannot be
* opened.
*/
public boolean isLeaf(Object value) {
if (value instanceof CategoriaProxy){
if (((CategoriaProxy) value).getLivello() == 3) {
return true;
}
}
return false;
}
}
private class CategoryDataProvider extends AsyncDataProvider<CategoriaProxy>
{
#Override
protected void onRangeChanged(HasData<CategoriaProxy> display)
{
requests.categoriaRequest().findAllCategorias(0, 8).with().fire(new Receiver<List<CategoriaProxy>>() {
#Override
public void onSuccess(List<CategoriaProxy> values) {
updateRowCount(values.size(), true);
updateRowData(0, values);
}
});
}
}
it Works.
Apparently it is not enough to change the data provider and refresh it.
You need also to force the affected cell to close and reopen it, as in this example
public void updateCellBrowser(String id) {
TreeNode node = getNode(cellBrowser.getRootTreeNode(),id);
if(node != null && ! node.isDestroyed()) {
TreeNode parent = node.getParent();
int index = node.getIndex();
parent.setChildOpen(index, false,true);
parent.setChildOpen(index, true, true);
}
}
In my particular example the cell ids are pathnames hence the following
implementation of getNode().
private TreeNode getNode(TreeNode node, String id) {
for(int i=0; i < node.getChildCount(); i++)
if(node.isChildOpen(i)) {
Object value = node.getChildValue(i);
if(value instanceof String) {
String nodeId = ((String) value);
if(id.equals(nodeId))
return node.setChildOpen(i, true);
if(id.startsWith(nodeId))
getNode(node.setChildOpen(i, true),id);
}
}
return null;
}
Easily reproducible in GWT 1.6.4:
Tree tree = new Tree();
tree.addItem(new TextBox());
The problem lies with onBrowserEvent in Tree:
switch (eventType) {
case Event.ONKEYDOWN:
case Event.ONKEYUP: {
if (isArrowKey(DOM.eventGetKeyCode(event))) {
DOM.eventCancelBubble(event, true);
DOM.eventPreventDefault(event);
return;
}
}
Like a lot of GWT widgets, they don't subclass well. There has to be a simple trick I could swing for this?
Solved this with a bit of a hack.
m_tree = new Tree()
{
#Override
protected boolean isKeyboardNavigationEnabled(TreeItem inCurrentItem)
{
return false;
}
#Override
public void onBrowserEvent(Event event) {
int eventType = DOM.eventGetType(event);
switch (eventType)
{
case Event.ONKEYDOWN:
case Event.ONKEYPRESS:
case Event.ONKEYUP:
return;
default:
break;
}
super.onBrowserEvent(event);
}
};