这是indexloc提供的服务,不要输入任何密码
Skip to content

Add fromSavedState handle method to generated nav args #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ class JavaNavWriter(private val useAndroidX: Boolean = true) : NavWriter<JavaCod
?: throw IllegalStateException("Destination with arguments must have name")
val className = ClassName.get(destName.packageName(), "${destName.simpleName()}Args")
val args = destination.args
val specs =
ClassWithArgsSpecs(args, annotations)
val specs = ClassWithArgsSpecs(args, annotations)

val fromBundleMethod = MethodSpec.methodBuilder("fromBundle").apply {
addAnnotation(annotations.NONNULL_CLASSNAME)
Expand All @@ -216,34 +215,30 @@ class JavaNavWriter(private val useAndroidX: Boolean = true) : NavWriter<JavaCod
addStatement("$T $N = new $T()", className, result, className)
addStatement("$N.setClassLoader($T.class.getClassLoader())", bundle, className)
args.forEach { arg ->
beginControlFlow("if ($N.containsKey($S))", bundle, arg.name).apply {
addStatement("$T $N", arg.type.typeName(), arg.sanitizedName)
addReadSingleArgBlock("containsKey", bundle, result, arg, specs) {
arg.type.addBundleGetStatement(this, arg, arg.sanitizedName, bundle)
addNullCheck(arg, arg.sanitizedName)
addStatement(
"$result.$N.put($S, $N)",
specs.hashMapFieldSpec,
arg.name,
arg.sanitizedName
)
}
if (arg.defaultValue == null) {
nextControlFlow("else")
addStatement(
"throw new $T($S)", IllegalArgumentException::class.java,
"Required argument \"${arg.name}\" is missing and does " +
"not have an android:defaultValue"
)
} else {
nextControlFlow("else")
addStatement(
"$result.$N.put($S, $L)",
specs.hashMapFieldSpec,
arg.name,
arg.defaultValue.write()
)
}
addStatement("return $N", result)
}.build()

val fromSavedStateHandleMethod = MethodSpec.methodBuilder("fromSavedStateHandle").apply {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of shared code between this method spec and fromBundleMethod, lets try to combine it by creating a local function with the common code and then invoking it with the distinct, something like this:

fun MethodSpec.Builder.commonFromArgGetCode(
    fromVariableName: String,
    resultVariableName: String,
    arg: Argument,
    argGetBlock: MethodSpec.Builder.() -> Unit
) {
    beginControlFlow("if ($N.contains($S))", fromVariableName, arg.name).apply {
        argGetBlock()
        addNullCheck(arg, arg.sanitizedName)
        addStatement(
            "$resultVariableName.$N.put($S, $N)",
            specs.hashMapFieldSpec,
            arg.name,
            arg.sanitizedName
        )
    }
    if (arg.defaultValue == null) {
        nextControlFlow("else")
        addStatement(
            "throw new $T($S)", java.lang.IllegalArgumentException::class.java,
            "Required argument \"${arg.name}\" is missing and does " +
                "not have an android:defaultValue"
        )
    } else {
        nextControlFlow("else")
        addStatement(
            "$resultVariableName.$N.put($S, $L)",
            specs.hashMapFieldSpec,
            arg.name,
            arg.defaultValue.write()
        )
    }
    endControlFlow()
}

then you can use it like this:

val fromSavedStateHandleMethod = MethodSpec.methodBuilder("fromSavedStateHandle").apply {
    addAnnotation(annotations.NONNULL_CLASSNAME)
    addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    addAnnotation(specs.suppressAnnotationSpec)
    val savedStateHandle = "savedStateHandle"
    addParameter(
        ParameterSpec.builder(SAVED_STATE_HANDLE_CLASSNAME, savedStateHandle)
            .addAnnotation(specs.androidAnnotations.NONNULL_CLASSNAME)
            .build()
    )
    returns(className)
    val result = "__result"
    addStatement("$T $N = new $T()", className, result, className)
    args.forEach { arg ->
        commonFromArgGetCode(
            fromVariableName = savedStateHandle,
            resultVariableName = result,
            arg= arg
        ) {
            addStatement("$T $N", arg.type.typeName(), arg.sanitizedName)
            addStatement("$N = $N.get($S)", arg.sanitizedName, savedStateHandle, arg.name)
        }
    }
    addStatement("return $N", result)
}.build()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for the JavaNavWriter.

I also checked the KotlinNavWriter and the only thing that could make sense here is the else branch where we read default values, but given that it's only ~7 lines I'd keep it duplicated because I think it makes it more readable that way.

addAnnotation(annotations.NONNULL_CLASSNAME)
addModifiers(Modifier.PUBLIC, Modifier.STATIC)
addAnnotation(specs.suppressAnnotationSpec)
val savedStateHandle = "savedStateHandle"
addParameter(
ParameterSpec.builder(SAVED_STATE_HANDLE_CLASSNAME, savedStateHandle)
.addAnnotation(specs.androidAnnotations.NONNULL_CLASSNAME)
.build()
)
returns(className)
val result = "__result"
addStatement("$T $N = new $T()", className, result, className)
args.forEach { arg ->
addReadSingleArgBlock("contains", savedStateHandle, result, arg, specs) {
addStatement("$N = $N.get($S)", arg.sanitizedName, savedStateHandle, arg.name)
}
endControlFlow()
}
addStatement("return $N", result)
}.build()
Expand Down Expand Up @@ -298,6 +293,7 @@ class JavaNavWriter(private val useAndroidX: Boolean = true) : NavWriter<JavaCod
.addMethod(constructor)
.addMethod(fromMapConstructor)
.addMethod(fromBundleMethod)
.addMethod(fromSavedStateHandleMethod)
.addMethods(specs.getters())
.addMethod(specs.toBundleMethod("toBundle"))
.addMethod(specs.equalsMethod(className))
Expand All @@ -308,6 +304,42 @@ class JavaNavWriter(private val useAndroidX: Boolean = true) : NavWriter<JavaCod

return JavaFile.builder(className.packageName(), typeSpec).build().toCodeFile()
}

private fun MethodSpec.Builder.addReadSingleArgBlock(
containsMethodName: String,
sourceVariableName: String,
targetVariableName: String,
arg: Argument,
specs: ClassWithArgsSpecs,
addGetStatement: MethodSpec.Builder.() -> Unit
) {
beginControlFlow("if ($N.$containsMethodName($S))", sourceVariableName, arg.name)
addStatement("$T $N", arg.type.typeName(), arg.sanitizedName)
addGetStatement()
addNullCheck(arg, arg.sanitizedName)
addStatement(
"$targetVariableName.$N.put($S, $N)",
specs.hashMapFieldSpec,
arg.name,
arg.sanitizedName
)
nextControlFlow("else")
if (arg.defaultValue == null) {
addStatement(
"throw new $T($S)", IllegalArgumentException::class.java,
"Required argument \"${arg.name}\" is missing and does not have an " +
"android:defaultValue"
)
} else {
addStatement(
"$targetVariableName.$N.put($S, $L)",
specs.hashMapFieldSpec,
arg.name,
arg.defaultValue.write()
)
}
endControlFlow()
}
}

private class ClassWithArgsSpecs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ internal val ACTION_ONLY_NAV_DIRECTION_CLASSNAME: ClassName =
internal val NAV_ARGS_CLASSNAME: ClassName = ClassName.get("androidx.navigation", "NavArgs")
internal val HASHMAP_CLASSNAME: ClassName = ClassName.get("java.util", "HashMap")
internal val BUNDLE_CLASSNAME: ClassName = ClassName.get("android.os", "Bundle")
internal val SAVED_STATE_HANDLE_CLASSNAME: ClassName =
ClassName.get("androidx.lifecycle", "SavedStateHandle")
internal val PARCELABLE_CLASSNAME = ClassName.get("android.os", "Parcelable")
internal val SERIALIZABLE_CLASSNAME = ClassName.get("java.io", "Serializable")
internal val SYSTEM_CLASSNAME = ClassName.get("java.lang", "System")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,52 @@ class KotlinNavWriter(private val useAndroidX: Boolean = true) : NavWriter<Kotli
addStatement("return·%T(${tempVariables.joinToString(", ") { it }})", className)
}.build()

val fromSavedStateHandleFunSpec = FunSpec.builder("fromSavedStateHandle").apply {
addAnnotation(JvmStatic::class)
returns(className)
val savedStateParamName = "savedStateHandle"
addParameter(savedStateParamName, SAVED_STATE_HANDLE_CLASSNAME)
val tempVariables = destination.args.map { arg ->
val tempVal = "__${arg.sanitizedName}"
addStatement(
"val %L : %T",
tempVal,
arg.type.typeName().copy(nullable = true)
)
beginControlFlow("if (%L.contains(%S))", savedStateParamName, arg.name)
addStatement("%L = %L[%S]", tempVal, savedStateParamName, arg.name)
if (!arg.isNullable) {
beginControlFlow("if (%L == null)", tempVal)
val errorMessage = if (arg.type.allowsNullable()) {
"Argument \"${arg.name}\" is marked as non-null but was passed a null value"
} else {
"Argument \"${arg.name}\" of type ${arg.type} does not support null values"
}
addStatement(
"throw·%T(%S)",
IllegalArgumentException::class.asTypeName(),
errorMessage
)
endControlFlow()
}
nextControlFlow("else")
val defaultValue = arg.defaultValue
if (defaultValue != null) {
addStatement("%L = %L", tempVal, arg.defaultValue.write())
} else {
addStatement(
"throw·%T(%S)",
IllegalArgumentException::class.asTypeName(),
"Required argument \"${arg.name}\" is missing and does not have an " +
"android:defaultValue"
)
}
endControlFlow()
return@map tempVal
}
addStatement("return·%T(${tempVariables.joinToString(", ") { it }})", className)
}.build()

val typeSpec = TypeSpec.classBuilder(className)
.addSuperinterface(NAV_ARGS_CLASSNAME)
.addModifiers(KModifier.DATA)
Expand All @@ -283,6 +329,7 @@ class KotlinNavWriter(private val useAndroidX: Boolean = true) : NavWriter<Kotli
.addType(
TypeSpec.companionObjectBuilder()
.addFunction(fromBundleFunSpec)
.addFunction(fromSavedStateHandleFunSpec)
.build()
)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ internal val ACTION_ONLY_NAV_DIRECTION_CLASSNAME: ClassName =
ClassName("androidx.navigation", "ActionOnlyNavDirections")
internal val NAV_ARGS_CLASSNAME: ClassName = ClassName("androidx.navigation", "NavArgs")
internal val BUNDLE_CLASSNAME: ClassName = ClassName("android.os", "Bundle")
internal val SAVED_STATE_HANDLE_CLASSNAME: ClassName =
ClassName("androidx.lifecycle", "SavedStateHandle")

internal val PARCELABLE_CLASSNAME = ClassName("android.os", "Parcelable")
internal val SERIALIZABLE_CLASSNAME = ClassName("java.io", "Serializable")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.lifecycle.SavedStateHandle;
import androidx.navigation.NavArgs;
import java.lang.IllegalArgumentException;
import java.lang.Object;
Expand Down Expand Up @@ -54,6 +55,24 @@ public class MainFragment$InnerFragmentArgs implements NavArgs {
return __result;
}

@NonNull
@SuppressWarnings("unchecked")
public static MainFragment$InnerFragmentArgs fromSavedStateHandle(
@NonNull SavedStateHandle savedStateHandle) {
MainFragment$InnerFragmentArgs __result = new MainFragment$InnerFragmentArgs();
if (savedStateHandle.contains("mainArg")) {
String mainArg;
mainArg = savedStateHandle.get("mainArg");
if (mainArg == null) {
throw new IllegalArgumentException("Argument \"mainArg\" is marked as non-null but was passed a null value.");
}
__result.arguments.put("mainArg", mainArg);
} else {
throw new IllegalArgumentException("Required argument \"mainArg\" is missing and does not have an android:defaultValue");
}
return __result;
}

@SuppressWarnings("unchecked")
@NonNull
public String getMainArg() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.SavedStateHandle;
import androidx.navigation.NavArgs;
import java.io.Serializable;
import java.lang.IllegalArgumentException;
Expand Down Expand Up @@ -130,6 +131,95 @@ public static MainFragmentArgs fromBundle(@NonNull Bundle bundle) {
return __result;
}

@NonNull
@SuppressWarnings("unchecked")
public static MainFragmentArgs fromSavedStateHandle(@NonNull SavedStateHandle savedStateHandle) {
MainFragmentArgs __result = new MainFragmentArgs();
if (savedStateHandle.contains("main")) {
String main;
main = savedStateHandle.get("main");
if (main == null) {
throw new IllegalArgumentException("Argument \"main\" is marked as non-null but was passed a null value.");
}
__result.arguments.put("main", main);
} else {
throw new IllegalArgumentException("Required argument \"main\" is missing and does not have an android:defaultValue");
}
if (savedStateHandle.contains("optional")) {
int optional;
optional = savedStateHandle.get("optional");
__result.arguments.put("optional", optional);
} else {
__result.arguments.put("optional", -1);
}
if (savedStateHandle.contains("reference")) {
int reference;
reference = savedStateHandle.get("reference");
__result.arguments.put("reference", reference);
} else {
__result.arguments.put("reference", R.drawable.background);
}
if (savedStateHandle.contains("referenceZeroDefaultValue")) {
int referenceZeroDefaultValue;
referenceZeroDefaultValue = savedStateHandle.get("referenceZeroDefaultValue");
__result.arguments.put("referenceZeroDefaultValue", referenceZeroDefaultValue);
} else {
__result.arguments.put("referenceZeroDefaultValue", 0);
}
if (savedStateHandle.contains("floatArg")) {
float floatArg;
floatArg = savedStateHandle.get("floatArg");
__result.arguments.put("floatArg", floatArg);
} else {
__result.arguments.put("floatArg", 1F);
}
if (savedStateHandle.contains("floatArrayArg")) {
float[] floatArrayArg;
floatArrayArg = savedStateHandle.get("floatArrayArg");
if (floatArrayArg == null) {
throw new IllegalArgumentException("Argument \"floatArrayArg\" is marked as non-null but was passed a null value.");
}
__result.arguments.put("floatArrayArg", floatArrayArg);
} else {
throw new IllegalArgumentException("Required argument \"floatArrayArg\" is missing and does not have an android:defaultValue");
}
if (savedStateHandle.contains("objectArrayArg")) {
ActivityInfo[] objectArrayArg;
objectArrayArg = savedStateHandle.get("objectArrayArg");
if (objectArrayArg == null) {
throw new IllegalArgumentException("Argument \"objectArrayArg\" is marked as non-null but was passed a null value.");
}
__result.arguments.put("objectArrayArg", objectArrayArg);
} else {
throw new IllegalArgumentException("Required argument \"objectArrayArg\" is missing and does not have an android:defaultValue");
}
if (savedStateHandle.contains("boolArg")) {
boolean boolArg;
boolArg = savedStateHandle.get("boolArg");
__result.arguments.put("boolArg", boolArg);
} else {
__result.arguments.put("boolArg", true);
}
if (savedStateHandle.contains("optionalParcelable")) {
ActivityInfo optionalParcelable;
optionalParcelable = savedStateHandle.get("optionalParcelable");
__result.arguments.put("optionalParcelable", optionalParcelable);
} else {
__result.arguments.put("optionalParcelable", null);
}
if (savedStateHandle.contains("enumArg")) {
AccessMode enumArg;
enumArg = savedStateHandle.get("enumArg");
if (enumArg == null) {
throw new IllegalArgumentException("Argument \"enumArg\" is marked as non-null but was passed a null value.");
}
__result.arguments.put("enumArg", enumArg);
} else {
__result.arguments.put("enumArg", AccessMode.READ);
}
return __result;
}

@SuppressWarnings("unchecked")
@NonNull
public String getMain() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.lifecycle.SavedStateHandle;
import androidx.navigation.NavArgs;
import java.lang.IllegalArgumentException;
import java.lang.Object;
Expand Down Expand Up @@ -65,6 +66,35 @@ public static SanitizedMainFragmentArgs fromBundle(@NonNull Bundle bundle) {
return __result;
}

@NonNull
@SuppressWarnings("unchecked")
public static SanitizedMainFragmentArgs fromSavedStateHandle(
@NonNull SavedStateHandle savedStateHandle) {
SanitizedMainFragmentArgs __result = new SanitizedMainFragmentArgs();
if (savedStateHandle.contains("name.with.dot")) {
int nameWithDot;
nameWithDot = savedStateHandle.get("name.with.dot");
__result.arguments.put("name.with.dot", nameWithDot);
} else {
throw new IllegalArgumentException("Required argument \"name.with.dot\" is missing and does not have an android:defaultValue");
}
if (savedStateHandle.contains("name_with_underscore")) {
int nameWithUnderscore;
nameWithUnderscore = savedStateHandle.get("name_with_underscore");
__result.arguments.put("name_with_underscore", nameWithUnderscore);
} else {
throw new IllegalArgumentException("Required argument \"name_with_underscore\" is missing and does not have an android:defaultValue");
}
if (savedStateHandle.contains("name with spaces")) {
int nameWithSpaces;
nameWithSpaces = savedStateHandle.get("name with spaces");
__result.arguments.put("name with spaces", nameWithSpaces);
} else {
throw new IllegalArgumentException("Required argument \"name with spaces\" is missing and does not have an android:defaultValue");
}
return __result;
}

@SuppressWarnings("unchecked")
public int getNameWithDot() {
return (int) arguments.get("name.with.dot");
Expand Down
Loading