Wednesday, March 26, 2014

Launch Android App from Web Link in Browser

Most app have its official website, and you may want users to install or open your app if it is already installed when they visit the website. Here is a common solution using the intent-filter with http scheme.
In your android app, add this intent-filter in your AndroidManifest.xml so it can intercept HTTP URL and launch your app.
<activity android:name="MyMainActivity" android:label="@string/app_name" android:launchmode="singleTask" android:screenorientation="portrait" android:windowsoftinputmode="adjustPan">
 <intent-filter>
  <action android:name="android.intent.action.MAIN"/>
  <category android:name="android.intent.category.LAUNCHER"/>
 </intent-filter>
 <intent-filter>
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <category android:name="android.intent.category.LAUNCHER"/>
  <data android:scheme="http" android:host="myapp.com" android:path="download"/>
 </intent-filter>
</activity>
In this example, when you click a link with URL "http://myapp.com/download", a dialog will popup and let you choose to open it with the browser or your app if the app with this intent-filter is installed. If the app is not installed, the link will open in the browser by default.
But you might want it to be more intelligent that if the app is installed, it launches the app directly without popup dialog. To achieve this goal, you can change the http scheme and use a custom scheme that will not be caught by other apps, and give the link your custom scheme URL (e.g. myUniqueScheme://myapp). 
<data android:scheme="myUniqueScheme" android:host="myapp"/>
However, this could cause problem when the app is not installed, it cannot redirect user to the download webpage. After a lot of search, finally I find a solution from stackoverflow regarding how to handle custom url scheme. However, the solution provided couldn't work on Chrome browser. And I make some changes by using intent for Chrome browser, so it can open the Play Store and search your app if it is not installed. In the javascript webpage, add following function:
function openOrDownloadURL() {
 var AppURL = "myUniqueScheme://myapp";
 var DownloadURL = "http://myapp.com/download";

 if (navigator.userAgent.match(/Android/)) {

  if (navigator.userAgent.match(/Chrome/)) {
   window.location = "intent://myapp/#Intent;scheme=myUniqueScheme;package=com.myapp;end"

  } else {
   var iframe = document.createElement("iframe");
   iframe.style.border = "none";
   iframe.style.width = "1px";
   iframe.style.height = "1px";
   iframe.onload = function() {
    window.location = DownloadURL;
   };
   iframe.src = AppURL;
   document.body.appendChild(iframe);
  }

 } else {
  // Not Android mobile
  window.location = DownloadURL;
 }
}
For hyperlink, change the url to javascript:
<a href="javascript:openOrDownloadURL();"> Open App </a>
Now in both Chrome and Android browser by clicking the link, the app can be launched directly if it is installed. If the app is not installed, in the Android browser, it will redirect to the given download url; in the Chrome browser, it will launch Play Store and search the app by package name.

Tuesday, March 25, 2014

Cassandra unit test with Spring framework

To unit test a method that manipulates the Cassandra database, you can use cassandra-unit-spring to make life a lot easier. With this dependency, there is no need to launch the Cassandra database every time to run the test cases. Instead, it will start embedded Cassandra when the test is running.

First, add the cassandra-unit-spring dependency in your maven pom.xml, currently the latest version is 2.0.2.0
<dependency>
    <groupid>org.cassandraunit</groupid>
    <artifactid>cassandra-unit-spring</artifactid>
    <version>2.0.2.0</version>
</dependency>

Then write your configuration class, here you can import other configurations from .xml file using @ImportResource annotation
@Configuration
@ImportResource({"classpath:META-INF/spring/applicationContext*.xml"})
public class CassandraTestConfig {
 @Bean(destroyMethod = "close")
 public Cluster cluster() throws ConfigurationException, TTransportException, IOException, InterruptedException{

  EmbeddedCassandraServerHelper.startEmbeddedCassandra();
  Cluster cluster = Cluster.builder().addContactPoints("127.0.0.1").withPort(9142).build();

  return cluster;
 }
}

And your test class, in order to enable Autowired annotation in spring framework, "DependencyInjectionTestExecutionListener.class" should be included in TestExecutionListeners.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {CassandraTestConfig.class})
@TestExecutionListeners({CassandraUnitTestExecutionListener.class, DependencyInjectionTestExecutionListener.class})
@EmbeddedCassandra
public class CqlTitleNewsRepositoryTest {
 
 @Autowired
 private MyDataRepository myDataRepository;
 
 @Test
 public void testMyRepo() throws IOException {
  
  MyData myData = new MyData();
  myDataRepository.save(myData);
  // your assertions
}
Notes: 
1. If there are multiple test cases, after each test, all non-system keyspace will be dropped. You can either create the keyspace you need before each test, or use @CassandraDataSet annotation to include a dataset definition file to initialize the keyspace.
2. If you have prepared statements in your target code, you need to include "cassandra-all" dependency with version equal or above 2.0.3. Because the cassandra-all version included in cassandra-unit-spring 2.0.2.0 is 2.0.2, it can cause exception "IllegalStateException: Instrumentation is not set; Jamm must be set as -javaagent" as described here: CASSANDRA-6107 causes EmbeddedCassandraService to fail