I just noticed the Property class http://developer.android.com/reference/android/util/Property.html . I can see some explanation of it here http://developer.android.com/about/versions/android-4.0.html#api but dont really understand the use cases of it. Would be great if someone can point me to some code snippets where I can understand this more.
Property is a wrapper to a Reflection.
For example, you have an object
public class A {
private int fieldOfA;
private int fieldTwo;
private int fieldThree;
public void setFieldOfA(int a) {
fieldOfA = a;
}
public int getFieldOfA() {
return fieldOfA;
}
public void setFieldTwo(int a) {
fieldTwo = a;
}
public int getFieldTwo() {
return fieldTwo;
}
public void setFieldThree(int a) {
fieldThree = a;
}
public int getFieldThree() {
return fieldThree;
}
}
If you need to update phew fields, you have to know all their names in the update method without Properties
private void updateValues(final A a, final int value) {
a.setFieldOfA(value);
a.setFieldTwo(value);
a.setFieldThree(value);
}
With Properties you can update only the properties.
Property aProperty = Property.of(A.class, int.class, "fieldOfA");
Property bProperty = Property.of(A.class, int.class, "fieldTwo");
Property cProperty = Property.of(A.class, int.class, "fieldThree");
Collection<Property<A, Integer>> properties = new HashSet<>();
properties.add(aProperty);
properties.add(bProperty);
properties.add(cProperty);
updateValues(a, 10, properties);
And the method would be
private void updateValues(final A a, final int value, final Collection<Property<A, Integer>> properties) {
for (final Property<A, Integer> property : properties) {
property.set(a, value);
}
}
As laalto memtioned, property animations use a similar mechanism.
One example would be property animations. The Property class provides an abstraction for attributes that can be changed over time to perform an animation.
Related
In my Activity, I have a Training object member initialized during onCreate(). All the members of this object are set.
private Training mTraining; is a class member
public class Training extends BaseModel {
...
#SerializedName("state")
public TrainingState state;
....
public TrainingPreview() {
}
This object is got from server (JSON), and I had a converter on this state to ensure this enum can't be null (I use GSON engine):
public class TrainingStateConverter extends EnumConverter<TrainingState> {
public static final Type TYPE = new TypeToken<TrainingState>() {}.getType();
#Override
protected TrainingState deserialize(String value) {
return TrainingState.fromString(value);
}
#Override
protected TrainingState getUnknownValue() {
return TrainingState.UNKNOWN;
}
}
During the setup, I've created the exercise list with the listener to show a specific exercise:
private void refreshExercisesList() {
final Runnable showTrainingParts = new Runnable() {
public void run() {
int nbItems = mCardExercises.setExercises(mTraining.training, mTraining.state,
new FlatCardTrainingProfilePartExercisesView.OnClickExerciseListener() {
#Override
public void showPart(String trainingPartId, int index) {
onClickOnExercisesList(trainingPartId, index);
}
});
}
};
}
...
}
My onClickOnExercisesList() method:
private void onClickOnExercisesList(String trainingPartId, int index) {
...
switch (mTraining.state) {
...
This Activity code works perfectly since couple of months, but yesterday there was a NullPointerException on switch (mTraining.state) :
int com.xxx.model.training.TrainingState.ordinal()' on a null object reference
com.xxx.ui.training.TrainingActivity.onClickOnExercisesList
How is possible guys?
Thank you very much for your help!
This would occur if state did not appear in the JSON.
The TypeConverter is only used if there is a value in the JSON to convert. If the value isn't present, then there's nothing to convert, so the value is whatever the default is, which is null, because you didn't set it:
#SerializedName("state")
public TrainingState state;
To fix the issue, initialize the variable to a default value:
#SerializedName("state")
public TrainingState state = TrainingState.UNKNOWN;
Using two way data binding I'm trying to fit a double(Double) inside a EditText type field.
I have tried with converter functions (with #InverseMethod) and also tried to write a #BindingAdapter with #InverseBindingAdapter.
I think I may be missing something crucial cause:
"#={`` + muObject.myDecimal}"
reveals 'null' in the EditText field.
The #InverseFunction method crashes, and the binding adapter way did not work either...
Could someone please point me in the right direction?
Thanks
ViewModel code:
Note that the BaseObservableViewModel extends ViewModel from architecture components and contains the contents of the BaseObservable class (tip from Yigit Boyar).
Also note that none of the fields in the QualityControl class are observable.
At last: Note that the getter/setter for measurementKm are a test. measurementKm is Double field in the QualityControl class and I would prefer to bind directly to that field.
public final class QualityControlViewModel extends BaseObservableViewModel {
#SuppressWarnings("unused")
private static final String TAG = "QualityControlVM";
private QualityControl qualityControl;
private int position;
public String measurementKm = "";
QualityControlViewModel(Application application) {
super(application);
}
public QualityControl getQualityControl() {
return qualityControl;
}
public void setQualityControl(QualityControl qualityControl) {
this.qualityControl = qualityControl;
// Initialize massMeasurementPlaceSelected
if (isValidMassMeasurementPlace()) massMeasurementPlaceSelected = true;
setMeasurementKm(qualityControl.getMeasurementKm());
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
private Double getMeasurementKm() {
if (this.measurementKm.length() > 0) {
return Double.parseDouble(this.measurementKm);
} else {
return 0.0;
}
}
private void setMeasurementKm(Double measurementKm) {
if (qualityControl.getMeasurementKm() != null) {
this.measurementKm = String.valueOf(qualityControl.getMeasurementKm());
}
notifyChange();
}
}
I'm using a custom parcelable object called GameSettings to pass a number of settings between Activites within an Android app (developed using MonoDroid). The settings are stored as properties on this GameSettings class, and up until now they've all been simple integers which I've been able to parcel just fine using Parcel.WriteInt() and Parcel.ReadInt().
I've just added a new property to GameSettings called CelebrityNames which is of type List<string>, and I'm trying to pass this in the same way but when ReadStringList() is called the property gets populated with an empty list (despite a non-empty list being written to the parcel prior to this using WriteStringList()). The parcel is being passed from NameEntryActivity to GameRoundActivity.
GameSettings.cs
using System;
using System.Collections.Generic;
using Android.OS;
using Java.Interop;
using Object = Java.Lang.Object;
namespace Celebrities
{
public class GameSettings : Object, IParcelable
{
private static readonly GenericParcelableCreator<GameSettings> _creator
= new GenericParcelableCreator<GameSettings>((parcel) => new GameSettings(parcel));
[ExportField("CREATOR")]
public static GenericParcelableCreator<GameSettings> InitializeCreator()
{
return _creator;
}
public int NumberOfPlayers { get; set; }
public int NumberOfTeams { get; set; }
public int CelebritiesPerPlayer { get; set; }
public int SecondsPerRound { get; set; }
private List<string> _celebrityNames;
public List<string> CelebrityNames {
get
{
_celebrityNames.Shuffle ();
return _celebrityNames;
}
set
{
_celebrityNames = value;
}
}
public GameSettings (int players, int teams, int celebrities, int secondsPerRound)
{
NumberOfPlayers = players;
NumberOfTeams = teams;
CelebritiesPerPlayer = celebrities;
SecondsPerRound = secondsPerRound;
}
private GameSettings(Parcel parcel) : this(parcel.ReadInt (), parcel.ReadInt (), parcel.ReadInt (), parcel.ReadInt ())
{
if (_celebrityNames == null)
{
_celebrityNames = new List<string>();
}
parcel.ReadStringList (_celebrityNames);
}
public void WriteToParcel(Parcel dest, ParcelableWriteFlags flags)
{
dest.WriteInt (NumberOfPlayers);
dest.WriteInt (NumberOfTeams);
dest.WriteInt (CelebritiesPerPlayer);
dest.WriteInt (SecondsPerRound);
dest.WriteStringList (_celebrityNames);
}
public int DescribeContents()
{
return 0;
}
}
}
Note: I'm using the backing variable _celebrityNames for parcelling as I have a custom getter that shuffles the list, which isn't necessary at this point. The problem is the same whether using the property or the variable.
GenericParcelableCreator.cs
using System;
using Android.OS;
using Object = Java.Lang.Object;
namespace Celebrities
{
public sealed class GenericParcelableCreator<T> : Object, IParcelableCreator
where T : Object, new()
{
private readonly Func<Parcel, T> _createFunc;
public GenericParcelableCreator(Func<Parcel, T> createFromParcelFunc)
{
_createFunc = createFromParcelFunc;
}
public Object CreateFromParcel(Parcel source)
{
return _createFunc(source);
}
public Object[] NewArray(int size)
{
return new T[size];
}
}
}
I'm including the relevant code from the Activity classes below (these are not the complete files for brevity, please ask if you think it would be helpful to see the rest too).
NameEntryActivity.cs (where I'm passing the parcel from)
public class NameEntryActivity : Activity
{
...
private GameSettings _gameSettings;
private List<string> _celebrityNames;
protected override void OnCreate (Bundle savedInstanceState)
{
...
_gameSettings = (Intent.Extras.GetParcelable ("GameSettings") as GameSettings);
_celebrityNames = new List<string> ();
...
}
...
private void MoveToNextCelebrity()
{
...
_gameSettings.CelebrityNames = _celebrityNames;
var intent = new Intent (this, typeof(GameRoundActivity));
intent.PutExtra("GameSettings", _gameSettings);
StartActivity (intent);
...
}
}
GameRoundActivity.cs (where I'm passing the parcel to)
public class GameRoundActivity : Activity
{
private GameSettings _gameSettings;
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.GameRound);
_gameSettings = (Intent.Extras.GetParcelable ("GameSettings") as GameSettings);
}
}
This is my first time developing an Android app, so it may well be that I've made a mistake somewhere in implementing the parcelling framework or have misunderstood it. Equally I've been looking at this code for so long that maybe I'm just missing a more general silly mistake :)
Thanks in advance!
I switched to using a string array instead of a list and it's now working using Parcel.WriteStringArray() and Parcel.CreateStringArray().
Obviously this wouldn't be applicable in every situation though so I'm still interested in why this was happening!
I have an app set up using Mortar/Flow and Dagger 2. It seems to work except for when I switch between two views of the same class. The new view ends up with the previous view's presenter.
For example, I have a ConversationScreen that takes a conversationId as a constructor argument. The first time I create a ConversationScreen and add it to Flow it creates the ConversationView which injects itself with a Presenter which is created with the conversationId that was passed to the screen. If I then create a new ConversationScreen with a different conversationId, when the ConversationView asks for a Presenter, Dagger returns the old Presenter, because the scope has not yet closed on the previous ConversationScreen.
Is there a way for me to manually close the scope of the previous screen before I set up the new one? Or have I just set up the scoping wrong to begin with?
ConversationView
public class ConversationView extends RelativeLayout {
#Inject
ConversationScreen.Presenter presenter;
public ConversationView(Context context, AttributeSet attrs) {
super(context, attrs);
DaggerService.<ConversationScreen.Component>getDaggerComponent(context).inject(this);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
presenter.takeView(this);
}
#Override
protected void onDetachedFromWindow() {
presenter.dropView(this);
super.onDetachedFromWindow();
}
}
ConversationScreen
#Layout(R.layout.screen_conversation)
public class ConversationScreen extends Paths.ConversationPath implements ScreenComponentFactory<SomeComponent> {
public ConversationScreen(String conversationId) {
super(conversationId);
}
#Override
public String getTitle() {
title = Conversation.get(conversationId).getTitle();
}
#Override
public Object createComponent(SomeComponent parent) {
return DaggerConversationScreen_Component.builder()
.someComponent(parent)
.conversationModule(new ConversationModule())
.build();
}
#dagger.Component(
dependencies = SomeComponent.class,
modules = ConversationModule.class
)
#DaggerScope(Component.class)
public interface Component {
void inject(ConversationView conversationView);
}
#DaggerScope(Component.class)
#dagger.Module
public class ConversationModule {
#Provides
#DaggerScope(Component.class)
Presenter providePresenter() {
return new Presenter(conversationId);
}
}
#DaggerScope(Component.class)
static public class Presenter extends BasePresenter<ConversationView> {
private String conversationId;
#Inject
Presenter(String conversationId) {
this.conversationId = conversationId;
}
#Override
protected void onLoad(Bundle savedInstanceState) {
super.onLoad(savedInstanceState);
bindData();
}
void bindData() {
// Show the messages in the conversation
}
}
}
If you use the default ScreenScoper and PathContextFactory classes from Mortar/Flow example project, you will see that the name of the new scope to create is the name of the Screen class.
Because you want to navigate from one instance of ConversationScreen to another instance of ConversationScreen, the name of the new scope will be equal to the name of previous scope. Thus, you won't create a new Mortar scope but just reuse the previous one, which means reusing the same presenter.
What you need is to change the naming policy of the new scope. Rather than using only the name of the new screen class, add something else.
Easiest fix is to use the instance identifier: myScreen.toString().
Another better fix is to have a tracking of the screen/scope names.
Following example extracted from https://github.com/lukaspili/Mortar-architect
class EntryCounter {
private final SimpleArrayMap<Class, Integer> ids = new SimpleArrayMap<>();
int get(History.Entry entry) {
Class cls = entry.path.getClass();
return ids.containsKey(cls) ? ids.get(cls) : 0;
}
void increment(History.Entry entry) {
update(entry, true);
}
void decrement(History.Entry entry) {
update(entry, false);
}
private void update(History.Entry entry, boolean increment) {
Class cls = entry.path.getClass();
int id = ids.containsKey(cls) ? ids.get(cls) : 0;
ids.put(cls, id + (increment ? 1 : -1));
}
}
And then use this counter when creating new scope:
private ScopedEntry buildScopedEntry(History.Entry entry) {
String scopeName = String.format("ARCHITECT_SCOPE_%s_%d", entry.path.getClass().getName(), entryCounter.get(entry));
return new ScopedEntry(entry, MortarFactory.createScope(navigator.getScope(), entry.path, scopeName));
}
And in some other place, i'm incrementing/decrementing the counter if new scope is pushed or scope is detroyed.
The scope in ScreenScoper is based on a string, which if you create the same path, it will use the same name as it bases it on the class name of your path.
I solved this by removing some noise from the ScreenScoper, considering I'm not using #ModuleFactory in my Dagger2-driven project anyways.
public abstract class BasePath
extends Path {
public abstract int getLayout();
public abstract Object createComponent();
public abstract String getScopeName();
}
public class ScreenScoper {
public MortarScope getScreenScope(Context context, String name, Object screen) {
MortarScope parentScope = MortarScope.getScope(context);
return getScreenScope(parentScope, name, screen);
}
/**
* Finds or creates the scope for the given screen.
*/
public MortarScope getScreenScope(MortarScope parentScope, final String name, final Object screen) {
MortarScope childScope = parentScope.findChild(name);
if (childScope == null) {
BasePath basePath = (BasePath) screen;
childScope = parentScope.buildChild()
.withService(DaggerService.TAG, basePath.createComponent())
.build(name);
}
return childScope;
}
}
I have Enums with Exception number and description. I pass exception number to MessageHandler.
And then i want to handle them and show user message with error description.
What is the best way to itterate over results without using switch construction ?
I have a few solutions but i'm not sure which is better for android.
Thank you.
There's actually a fairly straight-forward way to do this, and to do it in a way that doesn't depend on a particular enum. I ran into this problem when wanting to use enums to populate different parts of the UI.
Here is a little bit of sample code to show you how I did it:
public int enumPosition(Enum<?> lookingFor, Enum<?>[] lookingIn)
{
for (int i = 0; i < lookingIn.length; i++)
{
if (lookingIn[i].getValue().equals(lookingFor.getValue()))
{
//Found Answer
}
}
}
Of course, depending on what you need there are good ways to setup your enum to allow for better access and searching, here's what I mean:
public enum ExampleEnum
{
firstValue("#1", 1),
secondValue("#2", 2),
thirdValue("#3", 3);
private final String id;
private final int somethingUseful;
ExampleEnum(String id, int usefulValue)
{
this.id = id;
this.somethingUseful = usefulValue;
}
public String getValue() { return id; }
public int getSomethingUseful () { return somethingUseful; }
public static ExampleEnumfromString(String text)
{
if (text != null)
{
for (ExampleEnumt : ExampleEnum.values())
{
if (text.equalsIgnoreCase(t.id))
{
return t;
}
}
}
//Usually best to throw an exception here
return ExampleEnum.firstValue;
}
//Useful for passing to adapters
public static String[] getValues()
{
String[] vals = new String[ExampleEnum.values().length];
int i = 0;
for (ExampleEnumt : ExampleEnum.values())
{
vals[i] = t.getValue();
i++;
}
return vals;
}
#Override
public String toString()
{
return getValue();
}
}
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.res.Resources;
import com.cyberneticscore.voliastatis.R;
public enum ExceptionEnums {
NO_ERROR(1),
CONNECTION_ERROR(2),
LOGIN_ERROR (3),
SSL_ERROR (4),
WRONG_DATE (5),
INTERRUPTED (6),
CLIENT_PROTOCOL_ERROR(7),
UNKNOWN_HOST_ERROR(8),
IO_ERROR (9),
XPATHER_ERROR(10);
private static final Map<Integer, String> map = new HashMap<Integer, String>();
private static Context context;
private static Resources reso;
public static void getContext(Context _context)
{
context =_context;
reso = context.getResources();
}
private int pos;
public static void putMapValues(){
//static{
map.put(NO_ERROR.getPos(), reso.getString(R.string.exceptions_no_error));
map.put(CONNECTION_ERROR.getPos(), reso.getString(R.string.exceptions_connection_error));
map.put(LOGIN_ERROR.getPos(), reso.getString(R.string.exceptions_login_error));
map.put(SSL_ERROR.getPos(), reso.getString(R.string.exceptions_ssl_error));
map.put(WRONG_DATE.getPos(), reso.getString(R.string.exceptions_wrong_date));
map.put(INTERRUPTED.getPos(), reso.getString(R.string.exceptions_interrupted));
map.put(CLIENT_PROTOCOL_ERROR.getPos(), reso.getString(R.string.exceptions_client_protocol_error));
map.put(UNKNOWN_HOST_ERROR.getPos(), reso.getString(R.string.exceptions_unknown_host_error));
map.put(IO_ERROR.getPos(), reso.getString(R.string.exceptions_io_error));
map.put(XPATHER_ERROR.getPos(), reso.getString(R.string.exceptions_xpather_error));
}
ExceptionEnums(int pos){
this.pos = pos;
}
public int getPos(){
return pos;
}
public static String getErrorDescription(int position_number){
return map.get(position_number);
}
}