Skip to content

Add Android module libraries to Flutter project #2474

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

Merged
merged 12 commits into from
Jul 11, 2018
Merged

Conversation

stevemessick
Copy link
Member

@devoncarew @pq

Analyzes the Android module and adds all libraries required to compile its code to the top-level Flutter project. This should fix problems related to not having indexed all the code in the Android module (i.e. completion, navigation, etc.)

It probably still needs work but I'm not sure what the issues are. It takes noticeably more time to open a project since a Gradle sync is required, but that is no worse than what Android Studio does for an Android project.

@devoncarew
Copy link
Member

Awesome! Looking now...

Copy link
Member

@devoncarew devoncarew left a comment

Choose a reason for hiding this comment

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

Several questions :)

import javax.swing.*;

public class AndroidModuleLibraryType extends LibraryType<AndroidModuleLibraryProperties> {
public static final String LIBRARY_NAME = "Android Libraries";
Copy link
Member

Choose a reason for hiding this comment

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

How will this show up in the UI? As the category in the Project view? If so, perhaps something which indicates that it comes from Flutter (and/or is automanaged)? Flutter Plugin Libraries?

Copy link
Member Author

Choose a reason for hiding this comment

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

screen shot 2018-07-10 at 11 54 51 am

Copy link
Member

Choose a reason for hiding this comment

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

I see the latest is Android Libraries. It could still be useful to give a hint where these libraries come from (the Flutter plugin dependencies), but YMMV.

//return gradleProjectSystem.getAvailableDependency(coordinate, includePreview);
Method finders = ReflectionUtil.getMethod(gradleProjectSystem.getClass(), "getAvailableDependency");
if (finders == null) {
LOG.error("No method found: GradleProjectSystem.getAvailableDependency()");
Copy link
Member

Choose a reason for hiding this comment

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

Is this an error we want to show to the user, or something we just want to log (at a warn level)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops! I forgot to change that. Done.

return (GradleCoordinate) finders.invoke(gradleProjectSystem, coordinate, includePreview);
}
catch (IllegalAccessException | InvocationTargetException e) {
LOG.error(e);
Copy link
Member

Choose a reason for hiding this comment

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

I assume this is a totally unexpected error? Does GradleProjectSystem.getAvailableDependency() itself declare any exceptions? If so, we should like declare them in this method, then instanceof check for them here, and if that's what the invoke threw, re-throw them here.

Copy link
Member Author

Choose a reason for hiding this comment

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

None of the exceptions will be thrown. The checking is all there to satisfy compilers and style guides. :) The method isn't used in 3.1 and exists in 3.2.

@@ -85,7 +85,20 @@ public ProjectSystemSyncManager getSyncManager() {
@SuppressWarnings("override")
@Nullable
public GradleCoordinate getAvailableDependency(@NotNull GradleCoordinate coordinate, boolean includePreview) {
return null;
//return gradleProjectSystem.getAvailableDependency(coordinate, includePreview);
Copy link
Member

Choose a reason for hiding this comment

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

When will we be able to remove the reflection, and can you add a todo: comment for us to come back then and remove it?

Copy link
Member Author

Choose a reason for hiding this comment

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

We need that reflection as long as we build for 3.1. I added a TODO.

@@ -40,5 +41,7 @@ public void runActivity(@NotNull Project project) {
// Unset this flag for all projects, mainly to ease the upgrade from 3.0.1 to 3.1.
// TODO(messick) Delete once 3.0.x has 0 7DA's.
FlutterProjectCreator.disableUserConfig(project);
// Monitor Android dependencies.
AndroidModuleLibraryManager.startWatching();
Copy link
Member

Choose a reason for hiding this comment

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

Is this generally solid, or should we have an experimental flag to try it out?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a flag.

doGradleSync(getProject(), (Project x) -> updateAndroidLibraryContent(x));
}

private Void updateAndroidLibraryContent(Project androidProject) {
Copy link
Member

Choose a reason for hiding this comment

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

void?

Copy link
Member Author

Choose a reason for hiding this comment

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

Getting rid of type-checking warnings was a hassle. If there's a better way to do it I'd like to know. Using "void" will cause a compilation error now, though.

doGradleSync(getProject(), (Project x) -> updateAndroidLibraryContent(x));
}

private Void updateAndroidLibraryContent(Project androidProject) {
Copy link
Member

Choose a reason for hiding this comment

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

Have androidProject be @Nonnullable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, missed that one. Done.

LibraryTable androidProjectLibraryTable = LibraryTablesRegistrar.getInstance().getLibraryTable(androidProject);
Library[] androidProjectLibraries = androidProjectLibraryTable.getLibraries();
if (androidProjectLibraries.length == 0) {
LOG.error("Gradle sync was incomplete -- no Android libraries found");
Copy link
Member

Choose a reason for hiding this comment

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

If we do want this to be a user-facing error, we should use the toast notifications (from the FlutterMessages utility class). If not, we should log at a warn level.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

}
}

private static void fileChanged(@NotNull final Project project, @NotNull final VirtualFile file) {
Copy link
Member

Choose a reason for hiding this comment

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

Just for my own edification, it looks like we watch the build.gradle (in any subdirectory?), and schedule a gradle sync when it changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, there are two of them (at least) in an Android module. We might want to watch settings.gradle too.

return ServiceManager.getService(project, AndroidModuleLibraryManager.class);
}

public static void startWatching() {
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this will only watch projects that are open at IntelliJ launch?

Copy link
Member Author

Choose a reason for hiding this comment

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

This runs each time a project is opened.

Copy link
Member

Choose a reason for hiding this comment

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

We're iterating over all open projects below, so we may want to instead just process the project that was opened (which, I think this information is available from the StartupActivity interface).

Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of those "What was I thinking?" moments. I removed the loop from startWatching() and added the Project as a parameter. Thanks for catching that!

@stevemessick
Copy link
Member Author

I made a few changes, PTAL.

@stevemessick
Copy link
Member Author

The latest change simplifies the library name. PTAL

screen shot 2018-07-11 at 7 25 16 am

There's no need to append the project name. The convention is:

  • Dart * contains Dart code
  • Flutter * contains Flutter code
  • Android * contains Android code

Copy link
Member

@devoncarew devoncarew left a comment

Choose a reason for hiding this comment

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

Awesome! Three main comments (also in-line):

  • we could choose to expose as a user-facing preference (defaulting to off) instead of a system property, if we think we're ready
  • we should evaluate the workflow that we want with this, esp. wrt when we should drive flutter_tools, and when it should drive gradle
  • we're iterating over all the projects when a project is opened, and we should likely just examine the specific one that was opened

import io.flutter.project.FlutterProjectCreator;
import io.flutter.utils.FlutterModuleUtils;
import org.jetbrains.annotations.NotNull;

import java.util.MissingResourceException;

public class FlutterStudioStartupActivity implements StartupActivity {
Copy link
Member

Choose a reason for hiding this comment

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

I think if we implement void runActivity(@NotNull Project project); here we can get the reference to the project that is being opened.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, done.

@@ -40,5 +44,10 @@ public void runActivity(@NotNull Project project) {
// Unset this flag for all projects, mainly to ease the upgrade from 3.0.1 to 3.1.
// TODO(messick) Delete once 3.0.x has 0 7DA's.
FlutterProjectCreator.disableUserConfig(project);
// Monitor Android dependencies.
if (System.getProperty("flutter.android.library.sync", null) != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Another option here is to add a user facing preference in the preferences (likely defaulting to off initially).

Copy link
Member Author

Choose a reason for hiding this comment

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

Could do; but later, I think.

}

public void update() {
doGradleSync(getProject(), (Project x) -> updateAndroidLibraryContent(x));
Copy link
Member

Choose a reason for hiding this comment

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

OK, one thing to consider:

gradle is being run for these projects, just not explicitly by the user. flutter run will drive gradle as necessary to build the app. flutter packages get already does som set up in terms of maintaining flutter plugin metadata (including editing gradle files?). So, we have options for having some portions of this being driven from the CLI, and us triggering the flutter CLI when appropriate.

return ServiceManager.getService(project, AndroidModuleLibraryManager.class);
}

public static void startWatching() {
Copy link
Member

Choose a reason for hiding this comment

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

We're iterating over all open projects below, so we may want to instead just process the project that was opened (which, I think this information is available from the StartupActivity interface).

import javax.swing.*;

public class AndroidModuleLibraryType extends LibraryType<AndroidModuleLibraryProperties> {
public static final String LIBRARY_NAME = "Android Libraries";
Copy link
Member

Choose a reason for hiding this comment

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

I see the latest is Android Libraries. It could still be useful to give a hint where these libraries come from (the Flutter plugin dependencies), but YMMV.


@Override
public void syncSucceeded(@NotNull Project project) {
callback.apply(androidProject);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to put the flag update in a finally? Something like:

try {
  callback.apply(androidProject);
} finally {
  isUpdating.set(false);
}

EDIT: I'm probably over-thinking.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that doesn't work too well because the callback runs async. Effectively removes the flag. Speaking from experience here :)

@stevemessick
Copy link
Member Author

I made a slight modification to the code, in response to @devoncarew's comment about looping over projects. I checked it out and the smoke tests still work as expected so I'll submit without further review. Thanks for the comments!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants