All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.lint.checks.FragmentDetector Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.android.tools.lint.checks;

import static com.android.SdkConstants.CLASS_FRAGMENT;
import static com.android.SdkConstants.CLASS_V4_FRAGMENT;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Detector.JavaPsiScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiAnonymousClass;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;

import java.util.Arrays;
import java.util.List;

/**
 * Checks that Fragment subclasses can be instantiated via
 * {link {@link Class#newInstance()}}: the class is public, static, and has
 * a public null constructor.
 * 

* This helps track down issues like * http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate * (and countless duplicates) */ public class FragmentDetector extends Detector implements JavaPsiScanner { /** Are fragment subclasses instantiatable? */ public static final Issue ISSUE = Issue.create( "ValidFragment", //$NON-NLS-1$ "Fragment not instantiatable", "From the Fragment documentation:\n" + "*Every* fragment must have an empty constructor, so it can be instantiated when " + "restoring its activity's state. It is strongly recommended that subclasses do not " + "have other constructors with parameters, since these constructors will not be " + "called when the fragment is re-instantiated; instead, arguments can be supplied " + "by the caller with `setArguments(Bundle)` and later retrieved by the Fragment " + "with `getArguments()`.", Category.CORRECTNESS, 6, Severity.FATAL, new Implementation( FragmentDetector.class, Scope.JAVA_FILE_SCOPE) ).addMoreInfo( "http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$ /** Constructs a new {@link FragmentDetector} */ public FragmentDetector() { } // ---- Implements JavaScanner ---- @Nullable @Override public List applicableSuperClasses() { return Arrays.asList(CLASS_FRAGMENT, CLASS_V4_FRAGMENT); } @Override public void checkClass(@NonNull JavaContext context, @NonNull PsiClass node) { if (node instanceof PsiAnonymousClass) { String message = "Fragments should be static such that they can be re-instantiated by " + "the system, and anonymous classes are not static"; PsiElement locationNode = JavaContext.findNameElement(node); if (locationNode == null) { locationNode = node; } context.report(ISSUE, locationNode, context.getLocation(locationNode), message); return; } JavaEvaluator evaluator = context.getEvaluator(); if (evaluator.isAbstract(node)) { return; } if (!evaluator.isPublic(node)) { String message = String.format("This fragment class should be public (%1$s)", node.getQualifiedName()); context.report(ISSUE, node, context.getNameLocation(node), message); return; } if (node.getContainingClass() != null && !evaluator.isStatic(node)) { String message = String.format( "This fragment inner class should be static (%1$s)", node.getQualifiedName()); context.report(ISSUE, node, context.getNameLocation(node), message); return; } boolean hasDefaultConstructor = false; boolean hasConstructor = false; for (PsiMethod constructor : node.getConstructors()) { hasConstructor = true; if (constructor.getParameterList().getParametersCount() == 0) { if (evaluator.isPublic(constructor)) { hasDefaultConstructor = true; } else { Location location = context.getNameLocation(constructor); context.report(ISSUE, constructor, location, "The default constructor must be public"); // Also mark that we have a constructor so we don't complain again // below since we've already emitted a more specific error related // to the default constructor hasDefaultConstructor = true; } } else { Location location = context.getNameLocation(constructor); // TODO: Use separate issue for this which isn't an error String message = "Avoid non-default constructors in fragments: " + "use a default constructor plus " + "`Fragment#setArguments(Bundle)` instead"; context.report(ISSUE, constructor, location, message); } } if (!hasDefaultConstructor && hasConstructor) { String message = String.format( "This fragment should provide a default constructor (a public " + "constructor with no arguments) (`%1$s`)", node.getQualifiedName()); context.report(ISSUE, node, context.getNameLocation(node), message); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy