Skip to content

Commit

Permalink
Implement DeepLinkDelegate to handle dispatches instead
Browse files Browse the repository at this point in the history
Three parts:
- Generate DeepLinkDelegate class with its single static `dispatchFrom(Activity)` method.
- Slim down the generated DeepLinkDispatchActivity to just use this delegate, and it will only be generated if there's no activity annotated with @DeepLinkActivity
- Generate a DeepLinkResult class to use on the fly to return results from `dispatchFrom()`
  - This is a little wonky because the current Api is just a Java library and not an AAR. Generating it is the only current way to make this class available in generated code.
  • Loading branch information
ZacSweers committed Mar 29, 2016
1 parent 8f0b8e6 commit e42aeda
Show file tree
Hide file tree
Showing 16 changed files with 451 additions and 209 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ allprojects {
def isCi() {
project.hasProperty('CI') && CI.equals('true')
}

task wrapper(type: Wrapper) {
gradleVersion = '2.12'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class DeepLinkProcessorTest {
.compilesWithoutError()
.and()
.generatesSources(
JavaFileObjects.forResource("DeepLinkActivity.java"),
JavaFileObjects.forResource("DeepLinkDispatchActivity.java"),
JavaFileObjects.forSourceString("DeepLinkLoader.java",
"package com.airbnb.deeplinkdispatch;\n"
+ "\n"
Expand Down Expand Up @@ -62,7 +62,7 @@ public class DeepLinkProcessorTest {
.compilesWithoutError()
.and()
.generatesSources(
JavaFileObjects.forResource("DeepLinkActivity.java"),
JavaFileObjects.forResource("DeepLinkDispatchActivity.java"),
JavaFileObjects.forSourceString("DeepLinkLoader.java",
"package com.airbnb.deeplinkdispatch;\n"
+ "\n"
Expand Down
103 changes: 0 additions & 103 deletions deeplinkdispatch-processor/src/test/resources/DeepLinkActivity.java

This file was deleted.

108 changes: 108 additions & 0 deletions deeplinkdispatch-processor/src/test/resources/DeepLinkDelegate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.airbnb.deeplinkdispatch;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import java.lang.AssertionError;
import java.lang.NullPointerException;
import java.lang.String;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public final class DeepLinkDelegate {
private static final String TAG = DeepLinkDelegate.class.getSimpleName();

private DeepLinkDelegate() {
throw new AssertionError("No instances.");
}

public static com.airbnb.deeplinkdispatch.DeepLinkResult dispatchFrom(Activity activity) {
if (activity == null) {
throw new NullPointerException("activity == null");
}
Intent sourceIntent = activity.getIntent();
Uri uri = sourceIntent.getData();
if (uri == null) {
return createResultAndNotify(activity, false, null, "No Uri in given activity's intent.");
}
DeepLinkLoader loader = new DeepLinkLoader();
loader.load();
String uriString = uri.toString();
DeepLinkEntry entry = loader.parseUri(uriString);
if (entry != null) {
DeepLinkUri deepLinkUri = DeepLinkUri.parse(uriString);
Map<String, String> parameterMap = entry.getParameters(uriString);
for (String queryParameter : deepLinkUri.queryParameterNames()) {
for (String queryParameterValue : deepLinkUri.queryParameterValues(queryParameter)) {
if (parameterMap.containsKey(queryParameter)) {
Log.w(TAG, "Duplicate parameter name in path and query param: " + queryParameter);
}
parameterMap.put(queryParameter, queryParameterValue);
}
}
parameterMap.put(DeepLink.URI, uri.toString());
try {
Class<?> c = entry.getActivityClass();
Intent newIntent;
if (entry.getType() == DeepLinkEntry.Type.CLASS) {
newIntent = new Intent(activity, c);
} else {
Method method = c.getMethod(entry.getMethod(), Context.class);
newIntent = (Intent) method.invoke(c, activity);
}
if (sourceIntent.getAction() == null) {
newIntent.setAction(sourceIntent.getAction());
}
if (sourceIntent.getData() == null) {
newIntent.setData(sourceIntent.getData());
}
Bundle parameters;
if (sourceIntent.getExtras() != null) {
parameters = new Bundle(sourceIntent.getExtras());
} else {
parameters = new Bundle();
}
for (Map.Entry<String, String> parameterEntry : parameterMap.entrySet()) {
parameters.putString(parameterEntry.getKey(), parameterEntry.getValue());
}
newIntent.putExtras(parameters);
newIntent.putExtra(DeepLink.IS_DEEP_LINK, true);
if (activity.getCallingActivity() != null) {
newIntent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
activity.startActivity(newIntent);
return createResultAndNotify(activity, true, uri, null);
} catch (NoSuchMethodException exception) {
return createResultAndNotify(activity, false, uri, "Deep link to non-existent method: " + entry.getMethod());
} catch (IllegalAccessException exception) {
return createResultAndNotify(activity, false, uri, "Could not deep link to method: " + entry.getMethod());
} catch (InvocationTargetException exception) {
return createResultAndNotify(activity, false, uri, "Could not deep link to method: " + entry.getMethod());
}
} else {
return createResultAndNotify(activity, false, uri, "No registered entity to handle deep link: " + uri.toString());
}
}

private static com.airbnb.deeplinkdispatch.DeepLinkResult createResultAndNotify(Context context, final boolean successful, final Uri uri, final String error) {
com.airbnb.deeplinkdispatch.DeepLinkResult result = new com.airbnb.deeplinkdispatch.DeepLinkResult(successful, uri, error);
notifyListener(context, !successful, uri, error);
return result;
}

private static void notifyListener(Context context, boolean isError, Uri uri, String errorMessage) {
Intent intent = new Intent();
intent.setAction(DeepLinkActivity.ACTION);
intent.putExtra(DeepLinkActivity.EXTRA_URI, uri.toString());
intent.putExtra(DeepLinkActivity.EXTRA_SUCCESSFUL, !isError);
if (isError) {
intent.putExtra(DeepLinkActivity.EXTRA_ERROR_MESSAGE, errorMessage);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.airbnb.deeplinkdispatch;

import android.app.Activity;
import android.os.Bundle;
import java.lang.Override;

public class DeepLinkDispatchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DeepLinkDelegate.dispatchFrom(this);
}
}
68 changes: 68 additions & 0 deletions deeplinkdispatch-processor/src/test/resources/DeepLinkResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.airbnb.deeplinkdispatch;

import android.net.Uri;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;

public final class DeepLinkResult {
private final boolean successful;

private final Uri uri;

private final String errorMessage;

DeepLinkResult(boolean successful, Uri uri, String errorMessage) {
this.successful = successful;
this.uri = uri;
this.errorMessage = errorMessage;
}

/**
* @return whether or not the dispatch was a success.
*/
public boolean isSuccessful() {
return successful;
}

/**
* @return this result's uri, or {@code null} if there is none.
*/
public Uri uri() {
return uri;
}

/**
* @return this result's error message, or {@code null} if there is none.
*/
public String errorMessage() {
return errorMessage;
}

@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }

DeepLinkResult that = (DeepLinkResult) o;

if (successful != that.successful) { return false; }
if (uri != null ? !uri.equals(that.uri) : that.uri != null) { return false; }
return errorMessage != null ? errorMessage.equals(that.errorMessage) : that.errorMessage == null;}

@Override
public int hashCode() {
int result = (successful ? 1 : 0);
result = 31 * result + (uri != null ? uri.hashCode() : 0);
result = 31 * result + (errorMessage != null ? errorMessage.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "DeepLinkResult{" +
"successful=" + successful +
", uri=" + uri +
", errorMessage='" + errorMessage + '\'' +
'}';}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2015 Airbnb, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.airbnb.deeplinkdispatch;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that this activity will receive deep links, so the processor knows not to generate
* a separate activity.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface DeepLinkActivity {
String ACTION = "com.airbnb.deeplinkdispatch.DEEPLINK_ACTION";
String EXTRA_SUCCESSFUL = "com.airbnb.deeplinkdispatch.EXTRA_SUCCESSFUL";
String EXTRA_URI = "com.airbnb.deeplinkdispatch.EXTRA_URI";
String EXTRA_ERROR_MESSAGE = "com.airbnb.deeplinkdispatch.EXTRA_ERROR_MESSAGE";
}
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Tue Mar 22 02:29:21 PDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip
4 changes: 2 additions & 2 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ dependencies {
apt project(':deeplinkdispatch-processor')

compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:appcompat-v7:23.2.1'

testCompile 'org.robolectric:robolectric:3.0-rc3'
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'junit:junit:4.12'
}
2 changes: 1 addition & 1 deletion sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
android:label="@string/title_second"
/>
<activity
android:name="com.airbnb.deeplinkdispatch.DeepLinkActivity"
android:name="com.airbnb.deeplinkdispatch.DeepLinkDispatchActivity"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.airbnb.deeplinkdispatch.DeepLinkActivity;


public class DeepLinkReceiver extends BroadcastReceiver {
private static final String TAG = DeepLinkReceiver.class.getSimpleName();

Expand Down
Loading

0 comments on commit e42aeda

Please sign in to comment.