Categories
discuss

mkdir() works while inside internal flash storage, but not SD card?

I am currently building a file management app that allows the user to browse the file system of their device. The user starts off in the root directory / of their device, but can browse to any location they want such as the internal flash storage or SD card.

One of the critical requirements of this app is to allow the user to create new folders anywhere. A feature like this would be immensely useful for the app. However, the File#mkdir() method does not work at all in the SD card directory.

I added the appropriate permissions to the manifest file. I also wrote a test to see which directories (all of which exist on my Lollipop 5.0 device) allow the creation of a new folder. From my observations, File#mkdir() only works when inside the internal flash storage directory.

Note: please don’t confuse Environment#getExternalStorageDirectory() with the SD card location, as explained by this article. Also on Lollipop 5.0, I believe /storage/emulated/0/ and /storage/sdcard0/ refer to the internal flash storage while /storage/emulated/1/ and /storage/sdcard1/ refer to the SD card (which is at least true for the device I am testing with).

How can I create new files and folders in areas outside the external storage path on non-rooted Android devices?


Manifest:

...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...

Test:

...
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final String NEW_FOLDER_NAME = "TestFolder";
        testPath(new File(Environment.getExternalStorageDirectory(), NEW_FOLDER_NAME));
        testPath(new File("/storage/emulated/0/", NEW_FOLDER_NAME));
        testPath(new File("/storage/emulated/1/", NEW_FOLDER_NAME));
        testPath(new File("/storage/sdcard0/Download/", NEW_FOLDER_NAME));
        testPath(new File("/storage/sdcard1/Pictures/", NEW_FOLDER_NAME));
    }

    private void testPath(File path) {
        String TAG = "Debug.MainActivity.java";
        String FOLDER_CREATION_SUCCESS = " mkdir() success: ";

        boolean success = path.mkdir();
        Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
        path.delete();
    }
}

Output:

/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/1/TestFolder mkdir() success: false
/storage/sdcard0/Download/TestFolder mkdir() success: true
/storage/sdcard1/Pictures/TestFolder mkdir() success: false

Answer

First, you should note that file.mkdir() and file.mkdirs() returns false if the directory already existed. If you want to know whether the directory exists on return, either use (file.mkdir() || file.isDirectory()) or simply ignore the return value and call file.isDirectory() (see the documentation).

That said, your real problem is that you need permission to create the directory on removable storage on Android 5.0+. Working with removable SD cards on Android is horrendous.

On Android 4.4 (KitKat), Google restricted access to SD cards (see here, here, and here). See this StackOverflow answer which leads to this XDA post if you need to create a directory on a removable SD card on Android 4.4 (KitKat).

On Android 5.0 (Lollipop), Google introduced new SD card access APIs. For sample usage please refer to this stackoverflow answer.

Basically, you need to use DocumentFile#createDirectory(String displayName) to create your directory. You will need to ask the user to grant permissions to your app before creating this directory.


NOTE: This is for removable storage. Using File#mkdirs() will work on internal storage (which is often confused with external storage on Android) if you have the permission android.permission.WRITE_EXTERNAL_STORAGE.


I will post some example code below:

Check if you need to ask for permission:

File sdcard = ... // the removable SD card
List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
DocumentFile documentFile = null;
boolean needPermissions = true;

for (UriPermission permission : permissions) {
  if (permission.isWritePermission()) {
    documentFile = DocumentFile.fromTreeUri(context, permission.getUri());
    if (documentFile != null) {
      if (documentFile.lastModified() == sdcard.lastModified()) {
        needPermissions = false;
        break;
      }
    }
  }
}

Next (if needPermissions is true), you can display a dialog to explain to the user that they need to select the “SD Card” to give your app permissions to create files/directories and then start the following activity:

if (needPermissions) {
  // show a dialog explaining that you need permission to create the directory
  // here, we will just launch to chooser (what you need to do after showing the dialog)
  startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), STORAGE_REQUEST_CODE);
} else {
  // we already have permission to write to the removable SD card
  // use DocumentFile#createDirectory
}

You will now need to check the resultCode and requestCode in onActivityResult:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == STORAGE_REQUEST_CODE && resultCode == RESULT_OK) {
    File sdcard = ... // get the removable SD card

    boolean needPermissions = true;
    DocumentFile documentFile = DocumentFile.fromTreeUri(MainActivity.this, data.getData());
    if (documentFile != null) {
      if (documentFile.lastModified() == sdcard.lastModified()) {
        needPermissions = false;
      }
    }

    if (needPermissions) {
      // The user didn't select the "SD Card".
      // You should try the process over again or do something else.
    } else {
      // remember this permission grant so we don't need to ask again.
      getContentResolver().takePersistableUriPermission(data.getData(),
          Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
      // Now we can work with DocumentFile and create our directory
      DocumentFile doc = DocumentFile.fromTreeUri(this, data.getData());
      // do stuff...
    }
    return;
  }
  super.onActivityResult(requestCode, resultCode, data);
}

That should give you a good start on working with DocumentFile and removable SD cards on Android 5.0+. It can be a PITA.


Also, there is no public API to get the path to a removable SD card (if one even exists). You should not rely on hardcoding "/storage/sdcard1"! There are quite a few posts about it on StackOverflow. Many of the solutions use the environment variable SECONDARY_STORAGE. Below is two methods you can use to find removable storage devices:

public static List<File> getRemovabeStorages(Context context) throws Exception {
  List<File> storages = new ArrayList<>();

  Method getService = Class.forName("android.os.ServiceManager")
      .getDeclaredMethod("getService", String.class);
  if (!getService.isAccessible()) getService.setAccessible(true);
  IBinder service = (IBinder) getService.invoke(null, "mount");

  Method asInterface = Class.forName("android.os.storage.IMountService$Stub")
      .getDeclaredMethod("asInterface", IBinder.class);
  if (!asInterface.isAccessible()) asInterface.setAccessible(true);
  Object mountService = asInterface.invoke(null, service);

  Object[] storageVolumes;
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    String packageName = context.getPackageName();
    int uid = context.getPackageManager().getPackageInfo(packageName, 0).applicationInfo.uid;
    Method getVolumeList = mountService.getClass().getDeclaredMethod(
        "getVolumeList", int.class, String.class, int.class);
    if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
    storageVolumes = (Object[]) getVolumeList.invoke(mountService, uid, packageName, 0);
  } else {
    Method getVolumeList = mountService.getClass().getDeclaredMethod("getVolumeList");
    if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
    storageVolumes = (Object[]) getVolumeList.invoke(mountService, (Object[]) null);
  }

  for (Object storageVolume : storageVolumes) {
    Class<?> cls = storageVolume.getClass();
    Method isRemovable = cls.getDeclaredMethod("isRemovable");
    if (!isRemovable.isAccessible()) isRemovable.setAccessible(true);
    if ((boolean) isRemovable.invoke(storageVolume, (Object[]) null)) {
      Method getState = cls.getDeclaredMethod("getState");
      if (!getState.isAccessible()) getState.setAccessible(true);
      String state = (String) getState.invoke(storageVolume, (Object[]) null);
      if (state.equals("mounted")) {
        Method getPath = cls.getDeclaredMethod("getPath");
        if (!getPath.isAccessible()) getPath.setAccessible(true);
        String path = (String) getPath.invoke(storageVolume, (Object[]) null);
        storages.add(new File(path));
      }
    }
  }

  return storages;
}

public static File getRemovabeStorageDir(Context context) {
  try {
    List<File> storages = getRemovabeStorages(context);
    if (!storages.isEmpty()) {
      return storages.get(0);
    }
  } catch (Exception ignored) {
  }
  final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
  if (SECONDARY_STORAGE != null) {
    return new File(SECONDARY_STORAGE.split(":")[0]);
  }
  return null;
}
Categories
discuss

How to use a class customization to resolve file generating conflicts

I am trying to use Maven to generate JAXB files to be used by Spring framework, but Maven displays following errors:

I understand that it is unable to generate files with the names, but I am not sure how to resolve the issue. So far, I visited following links. 1, 2, 3

org.xml.sax.SAXParseException; systemId: http://www5v80.elsyarres.net/service.asmx?wsdl; lineNumber: 5; columnNumber: 39; A class/interface with the same name "hello.wsdl.SearchFlights" is already in use. Use a class customization to resolve this conflict.
....
org.xml.sax.SAXParseException; systemId: http://www5v80.elsyarres.net/service.asmx?wsdl; lineNumber: 12; columnNumber: 43; (Relevant to above error) another "SearchFlights" is generated from here.
....
org.xml.sax.SAXParseException; systemId: http://www5v80.elsyarres.net/service.asmx?wsdl; lineNumber: 371; columnNumber: 42; A class/interface with the same name "hello.wsdl.GetFlightDetails" is already in use. Use a class customization to resolve this conflict.
....

Maven plugin

    <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.4</version>
        <configuration>
            <warSourceDirectory>WebContent</warSourceDirectory>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.jvnet.jaxb2.maven2</groupId>
        <artifactId>maven-jaxb2-plugin</artifactId>
        <version>0.12.3</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <schemaLanguage>WSDL</schemaLanguage>
            <generatePackage>hello.wsdl</generatePackage>
            <schemas>
                <schema>
                    <url>http://www5v80.elsyarres.net/service.asmx?wsdl</url>
                </schema>
            </schemas>
        </configuration>
    </plugin>

I added following package-info.java file to the hello.wsdl package but it did not help.

@XmlSchema( 
    namespace = "ElsyArres.API",
    elementFormDefault = XmlNsForm.QUALIFIED) 
package hello.wsdl;

import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

Answer

The error message you are facing basically states that some names in the the typessection of your wsdl are you used two times. In your case all <element>tags have the same name as their corresponding types (defined as <complexType>).

Example:

  <s:element name="SearchFlights">
    <s:complexType>
      <s:sequence>
        <s:element minOccurs="0" maxOccurs="1" name="SoapMessage" type="tns:SearchFlights" />
      </s:sequence>
    </s:complexType>
  </s:element>

  <s:complexType name="SearchFlights">
    <s:complexContent mixed="false">
      <s:extension base="tns:SoapMessageBase">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1" name="Request" type="tns:SearchFlightsRequest" />
          <s:element minOccurs="0" maxOccurs="1" name="Response" type="tns:SearchFlightsResponse" />
        </s:sequence>
      </s:extension>
    </s:complexContent>
  </s:complexType>

This is quite uncommon.

There are basically two options to resolve these issues:

Use autoNameResolution

 <plugin>
     <groupId>org.jvnet.jaxb2.maven2</groupId>
     <artifactId>maven-jaxb2-plugin</artifactId>
     <version>0.13.1</version>
     <executions>
         <execution>
             <goals>
                 <goal>generate</goal>
             </goals>
         </execution>
     </executions>
     <configuration>

         <args>
             <arg>-XautoNameResolution</arg>
         </args>

         <schemaLanguage>WSDL</schemaLanguage>
         <generatePackage>hello.wsdl</generatePackage>
         <schemas>
             <schema>
                 <url>http://www5v80.elsyarres.net/service.asmx?wsdl</url>
             </schema>
          </schemas>
      </configuration>
  </plugin>

The plugin will resolve all naming conflicts through appending numbers to every colliding name. In the above mentioned case of SearchFlights this will result in SearchFlights and SearchFlights2 being generated.

A better way would be to use a binding file to resolve all name conflicts in advance. Binding files mostly contain XPATHexpression and transformation rules. A binding file that appends to every declarations name is the following:

<?xml version="1.0" encoding="UTF-8"?>
<jaxws:bindings wsdlLocation="http://www5v80.elsyarres.net/service.asmx?wsdl"
            xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
            xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" version="2.1"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <jaxws:bindings node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='ElsyArres.API']">
        <jaxb:schemaBindings>
            <jaxb:nameXmlTransform>
                <jaxb:elementName suffix="Elem"/>
            </jaxb:nameXmlTransform>
        </jaxb:schemaBindings>
    </jaxws:bindings>
</jaxws:bindings>

There are other options for jaxb:nameXmlTransform like suffixes and prepending to other kind of xml elements (like types).

Sadly i could not get to work this binding file with the org.jvnet.jaxb2.maven2:maven-jaxb2-plugin( but i am sure there is a working configuration)

It nevertheless works with the org.codehaus.mojo:jaxws-maven-plugin and the following configuration.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.4.1</version>
    <executions>
        <execution>
            <goals>
                <goal>wsimport</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <bindingFiles>
         <bindingFile>${basedir}/src/main/resources/bindings.xjb</bindingFile>
        </bindingFiles>
        <wsdlUrls>
            <wsdlUrl>http://www5v80.elsyarres.net/service.asmx?wsdl</wsdlUrl>
        </wsdlUrls>
        <vmArgs>
            <vmArg>-Djavax.xml.accessExternalSchema=all</vmArg>
        </vmArgs>
    </configuration>
</plugin>
Categories
discuss

How to convert Map to List in Java 8

How to convert a Map<String, Double> to List<Pair<String, Double>> in Java 8?

I wrote this implementation, but it is not efficient

Map<String, Double> implicitDataSum = new ConcurrentHashMap<>();
//....
List<Pair<String, Double>> mostRelevantTitles = new ArrayList<>();
implicitDataSum.entrySet()
               .stream()
               .sorted(Comparator.comparing(e -> -e.getValue()))
               .forEachOrdered(e -> mostRelevantTitles.add(new Pair<>(e.getKey(), e.getValue())));

return mostRelevantTitles;

I know that it should works using .collect(Collectors.someMethod()). But I don’t understand how to do that.

Answer

Well, you want to collect Pair elements into a List. That means that you need to map your Stream<Map.Entry<String, Double>> into a Stream<Pair<String, Double>>.

This is done with the map operation:

Returns a stream consisting of the results of applying the given function to the elements of this stream.

In this case, the function will be a function converting a Map.Entry<String, Double> into a Pair<String, Double>.

Finally, you want to collect that into a List, so we can use the built-in toList() collector.

List<Pair<String, Double>> mostRelevantTitles = 
    implicitDataSum.entrySet()
                   .stream()
                   .sorted(Comparator.comparing(e -> -e.getValue()))
                   .map(e -> new Pair<>(e.getKey(), e.getValue()))
                   .collect(Collectors.toList());

Note that you could replace the comparator Comparator.comparing(e -> -e.getValue()) by Map.Entry.comparingByValue(Comparator.reverseOrder()).

Categories
discuss

How to implement Swift-like enums with associated values in JavaScript?

The Swift language has a fantastic enum support. Not only can one define a standard enum with cases, but cases can have optional values “associated to them.”

For example, taken from the Swift docs:

enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
    case Other
}

Such that one could create a Barcode enum by passing in a value, like so:

var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)

and also switch on productBarcode at a later date to retrieve the associated value (a tuple of ints).


I have been trying to implement this kind of enum system in JavaScript (ES5, in particular), but am hitting a wall. What is the best way to structure an enum system, especially one with associated values?

Answer

That’s notr exactly the way enums work in most languages i know. Usually they are more like a way to type a value as one of these states. Like selecting one value out of a set of possible values. And to ensure type-safety in doing this, unlike with plain integers.

What you posted in your code, I would call a plain Object with factory-methods.

Since they are not supported that way by the language you have to implement them in a way that fit’s your needs as good as possible. So sum up what behaviour you expect.

In the mean time a Implementation based on the descriptions i’ve found on swift enums. hope it comes close to what you expect:

var odp = {
    ENUMERABLE: 4,

    //two helper with Object.defineProperty.
    value: function(obj, prop, v, flags){
        this.configurable = Boolean(flags & odp.CONFIGURABLE);
        this.writable = Boolean(flags & odp.WRITABLE);
        this.enumerable = Boolean(flags & odp.ENUMERABLE);
        this.value = v;
        Object.defineProperty(obj, prop, this);
        this.value = null;  //v may be a function or an object: remove the reference
        return obj;
    }.bind({    //caching the basic definition
        value: null, 
        configurable: false, 
        writable: false, 
        enumerable: false 
    }),

    accessor: function(obj, prop, getter, setter){
        this.get = getter || undefined;
        this.set = setter || undefined;
        Object.defineProperty(obj, prop, this);
        this.get = null;
        this.set = null;
        return obj;
    }.bind({ get: null, set: null })
}
//make these values immutable
odp.value(odp, "CONFIGURABLE", 1, odp.ENUMERABLE);
odp.value(odp, "WRITABLE", 2, odp.ENUMERABLE);
odp.value(odp, "ENUMERABLE", 4, odp.ENUMERABLE);



//Policy: 
//1. I don't f*** care wether the keys on the definition are own or inherited keys.
//since you pass them to me, I suppose you want me to process them.

//2. If i find some undefined-value i ignore it, as if it wasn't there.
//use null to represent some "empty" value

//name and extendProto are optional
function Enum(name, description, extendProto){
    var n = name, d = description, xp=extendProto;
    if(n && typeof n === "object") xp=d, d = n, n = null;
    var xpf = typeof xp === "function" && xp;
    var xpo = typeof xp === "object" && xp;

    function type(){ 
        throw new Error("enums are not supposed to be created manually"); 
    }

    //abusing filter() as forEach()
    //removing the keys that are undefined in the same step.
    var keys = Object.keys(d).filter(function(key){
        var val = d[key];
        if(val === undefined) return false;
        var proto = Object.create(type.prototype);

        //your chance to extend the particular prototype with further properties
        //like adding the prototype-methods of some other type
        var props = xpf || xpo && xpo[key];
        if(typeof props === "function") 
            props = props.call(type, proto, key, val);

        if(props && typeof props === "object" && props !== proto && props !== val){
            var flags = odp.CONFIGURABLE+odp.WRITABLE;
            for(var k in props) props[k]===undefined || odp.value(proto, k, props[k], flags);
            if("length" in props) odp.value(props, "length", props.length, flags);
        }

        if(typeof val === "function"){
            //a factory and typedefinition at the same type
            //call this function to create a new object of the type of this enum
            //and of the type of this function at the same time
            type[key] = function(){
                var me = Object.create(proto);
                var props = val.apply(me, arguments);
                if(props && typeof props === "object" && props !== me){
                    for(var k in props) props[k]===undefined || odp.value(me, k, props[k], odp.ENUMERABLE);
                    if("length" in props) odp.value(me, "length", props.length);
                }
                return me;
            }
            //fix the fn.length-property for this factory
            odp.value(type[key], "length", val.length, odp.CONFIGURABLE);

            //change the name of this factory
            odp.value(type[key], "name", (n||"enum")+"{ "+key+" }" || key, odp.CONFIGURABLE);

            type[key].prototype = proto;
            odp.value(proto, "constructor", type[key], odp.CONFIGURABLE);

        }else if(val && typeof val === "object"){
            for(var k in val) val[k] === undefined || odp.value(proto, k, val[k]);
            if("length" in val) odp.value(proto, "length", val.length);
            type[key] = proto;

        }else{
            //an object of the type of this enum that wraps the primitive
            //a bit like the String or Number or Boolean Classes

            //so remember, when dealing with this kind of values, 
            //you don't deal with actual primitives
            odp.value(proto, "valueOf", function(){ return val; });     
            type[key] = proto;

        }

        return true;
    });

    odp.value(type, "name", n || "enum[ " + keys.join(", ") + " ]", odp.CONFIGURABLE);
    Object.freeze(type);

    return type;
}

Beware, this code may need some further modification. Examples:

Factories

function uint(v){ return v>>>0 }

var Barcode = Enum("Barcode", {
    QRCode: function(string){
        //this refers to an object of both types, Barcode and Barcode.QRCode
        //aou can modify it as you wish
        odp.value(this, "valueOf", function(){ return string }, true);
    },

    UPCA: function(a,b,c,d){
        //you can also return an object with the properties you want to add
        //and Arrays, ...
        return [
            uint(a), 
            uint(b), 
            uint(c), 
            uint(d)
        ];
        //but beware, this doesn't add the Array.prototype-methods!!!

        //event this would work, and be processed like an Array
        return arguments;
    },

    Other: function(properties){ 
        return properties;  //some sugar
    }
});

var productBarcode = Barcode.UPCA(8, 85909, 51226, 3);
console.log("productBarcode is Barcode:", productBarcode instanceof Barcode);   //true
console.log("productBarcode is Barcode.UPCA:", productBarcode instanceof Barcode.UPCA); //true

console.log("productBarcode is Barcode.Other:", productBarcode instanceof Barcode.Other);   //false

console.log("accessing values: ", productBarcode[0], productBarcode[1], productBarcode[2], productBarcode[3], productBarcode.length);

Array.prototype.forEach.call(productBarcode, function(value, index){
    console.log("index:", index, "  value:", value);
});

Objects and Primitives

var indices = Enum({
    lo: { from: 0, to: 13 },
    hi: { from: 14, to: 42 },

    avg: 7
});

var lo = indices.lo;
console.log("lo is a valid index", lo instanceof indices);
console.log("lo is indices.lo", lo === indices.lo); 
//indices.lo always references the same Object
//no function-call, no getter!

var avg = indices.avg;  //beware, this is no primitive, it is wrapped

console.log("avg is a valid index", avg instanceof indices);
console.log("comparison against primitives:");
console.log(" - typesafe", avg === 7);  //false, since avg is wrapped!!!
console.log(" - loose", avg == 7);  //true
console.log(" - typecast+typesafe", Number(avg) === 7); //true

//possible usage like it was a primitive.
for(var i=lo.from; i<lo.to; ++i){
    console.log(i, i == avg);   //take a look at the first output ;)
}

//but if you want to use some of the prototype methods 
//(like the correct toString()-method on Numbers, or substr on Strings)
//make sure that you have a proper primitive!

var out = avg.toFixed(3);
//will fail since this object doesn't provide the prototype-methods of Number

//+avg does the same as Number(avg)
var out = (+avg).toFixed(3);    //will succeed

Identity

var def = { foo: 42 };

var obj = Enum({
    a: 13,
    b: 13,
    c: 13,

    obj1: def,
    obj2: def
});

//although all three have/represent the same value, they ain't the same
var v = obj.a;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //true, false, false

var v = obj.b;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //false, true, false

var v = obj.c;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //false, false, true


console.log("comparing objects", obj.obj1 === obj.obj2);    //false
console.log("comparing property foo", obj.obj1.foo === obj.obj2.foo);   //true

//same for the values provided by the factory-functions:
console.log("compare two calls with the same args:");
console.log("Barcode.Other() === Barcode.Other()", Barcode.Other() === Barcode.Other());
//will fail, since the factory doesn't cache, 
//every call creates a new Object instance.
//if you need to check wether they are equal, write a function that does that.

extendProto

//your chance to extend the prototype of each subordinated entry in the enum
//maybe you want to add some method from some other prototype 
//like String.prototype or iterator-methods, or a method for equality-checking, ...

var Barcode = Enum("Barcode", {/* factories */}, function(proto, key, value){
    var _barcode = this;    
    //so you can access the enum in closures, without the need for a "global" variable.
    //but if you mess around with this, you are the one to debug the Errors you produce.

    //this function is executed right after the prototpe-object for this enum-entry is created
    //and before any further modification.
    //neither this particular entry, nor the enum itself are done yet, so don't mess around with them.

    //the only purpose of this method is to provide you a hook 
    //to add further properties to the proto-object

    //aou can also return an object with properties to add to the proto-object.
    //these properties will be added as configurable and writable but not enumerable.
    //and no getter or setter. If you need more control, feel free to modify proto on you own.
    return {
        isBarcode: function(){
            return this instanceof _barcode;
        }
    }
});

//OR you can define it for every single property, 
//so you don't have to switch on the different properties in one huge function
var Barcode = Enum("Barcode", {/* factories */}, {
    "UPCA": function(proto, key, value){
        //same behaviour as the universal function
        //but will be executed only for the proto of UPCA

        var _barcode = this;    //aka Barcode in this case
        var AP = [];
        return { 
            //copy map and indexOf from the Array prototype
            map: AP.map,
            indexOf: AP.indexOf, 

            //and add a custom toString and clone-method to the prototype
            toString: function(){
                return "UPCA[ "+AP.join.call(this, ", ")+" ]";
            },
            clone: function(){
                return _barcode.UPCA.apply(null, this);
            } 
        };
    },

    //OR
    "QRCode": {
        //or simply define an object that contains the properties/methods 
        //that should be added to the proto of QRCode
        //again configurable and writable but not enumerable

        substr: String.prototype.substr,
        substring: String.prototype.substring,
        charAt: String.prototype.charAt,
        charCodeAt: String.prototype.charCodeAt
    }
});
//mixin-functions and objects can be mixed
Categories
discuss

How to mock a SharedPreferences using Mockito

I have just read about Unit Instrumented Testing in Android and I wonder how I can mock a SharedPreferences without any SharedPreferencesHelper class on it like here

My code is:

public class Auth {
private static SharedPreferences loggedUserData = null;
public static String getValidToken(Context context)
{
    initLoggedUserPreferences(context);
    String token = loggedUserData.getString(Constants.USER_TOKEN,null);
    return token;
}
public static String getLoggedUser(Context context)
{
    initLoggedUserPreferences(context);
    String user = loggedUserData.getString(Constants.LOGGED_USERNAME,null);
    return user;
}
public static void setUserCredentials(Context context, String username, String token)
{
    initLoggedUserPreferences(context);
    loggedUserData.edit().putString(Constants.LOGGED_USERNAME, username).commit();
    loggedUserData.edit().putString(Constants.USER_TOKEN,token).commit();
}

public static HashMap<String, String> setHeaders(String username, String password)
{
    HashMap<String, String> headers = new HashMap<String, String>();
    String auth = username + ":" + password;
    String encoding = Base64.encodeToString(auth.getBytes(), Base64.DEFAULT);
    headers.put("Authorization", "Basic " + encoding);
    return headers;
}

public static void deleteToken(Context context)
{
    initLoggedUserPreferences(context);
    loggedUserData.edit().remove(Constants.LOGGED_USERNAME).commit();
    loggedUserData.edit().remove(Constants.USER_TOKEN).commit();
}

public static HashMap<String, String> setHeadersWithToken(String token) {
    HashMap<String, String> headers = new HashMap<String, String>();
    headers.put("Authorization","Token "+token);
    return headers;
}
private static SharedPreferences initLoggedUserPreferences(Context context)
{
    if(loggedUserData == null)
        loggedUserData = context.getSharedPreferences(Constants.LOGGED_USER_PREFERENCES,0);
    return loggedUserData;
}}

Is is possible to mock SharedPreferences without creating other class on it?

Answer

So, because SharedPreferences comes from your context, it’s easy:

final SharedPreferences sharedPrefs = Mockito.mock(SharedPreferences.class);
final Context context = Mockito.mock(Context.class);
Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);

// no use context

for example, for getValidToken(Context context), the test could be:

@Before
public void before() throws Exception {
    this.sharedPrefs = Mockito.mock(SharedPreferences.class);
    this.context = Mockito.mock(Context.class);
    Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);
}

@Test
public void testGetValidToken() throws Exception {
    Mockito.when(sharedPrefs.getString(anyString(), anyString())).thenReturn("foobar");
    assertEquals("foobar", Auth.getValidToken(context));
    // maybe add some verify();
}
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..