Filesystem.mkdir not working right in Android Emulator

I am building a simple app that will download modules into internal storage in order to extend my app’s capabilities.
When my app initializes I check to see if the main storage folders need to be created, and create them if needed.

Here are the code snippets:

Personal service file for Filesystem commands (core.service.ts):

import { Injectable } from '@angular/core';
import { Filesystem, FilesystemDirectory, FilesystemEncoding } from '@capacitor/core';
import { Zip } from '@ionic-native/zip/ngx';

declare var require: any;

@Injectable({
providedIn: 'root'
})

export class CoreService {

constructor(private zip: Zip) {}

  async createSystemDirectories() {
    this.makePrivateDirectory();
    console.log('***** Done adding private directory');
    this.makeDirectory('books');
    console.log('***** Done adding books directory');
    this.makeDirectory('modules');
    console.log('***** one adding modules directory');
    let ls = this.readdir('private');
    console.log('***** Read private folder: ', ls);
  }

public makePrivateDirectory() {
		try {
      console.log('***** Creating private directory ('+FilesystemDirectory.Documents+'/private)');
			return Filesystem.mkdir({
				path: 'private',
				directory: FilesystemDirectory.Documents,
				recursive: false,
			});
		} catch (error) {
			console.error('***** Could not create directory ('+FilesystemDirectory.Documents+'/private): ' + error, error);
		}
	}

  public makeDirectory(path) {
    try {
      console.log('***** Creating directory directory ('+FilesystemDirectory.Documents+'/private/'+ path +')');
      return Filesystem.mkdir({
        path: 'private/' + path,
        directory: FilesystemDirectory.Documents,
        recursive: false,
      });
    } catch (error) {
      console.error('***** Could not create directory ('+FilesystemDirectory.Documents+'/private/' + path + '): ' + error, error);
    }
  }

  public readdir(dir) {
    try {console.log('***** Reading directory ('+FilesystemDirectory.Documents+'/private/'+ dir +')');
      return Filesystem.readdir({
        path: dir,
        directory: FilesystemDirectory.Documents
      });
    } catch(e) {
      console.error('***** Unable to read directory ('+ FilesystemDirectory.Documents +'/private/'+ dir +')', e);
    }
  }
...
}

Here is the log output based on my personal logging:

2020-11-03 15:04:49.121 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 203 - Msg: ***** Creating directory directory (DOCUMENTS/private)
2020-11-03 15:04:49.122 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 257 - Msg: ***** Done adding private directory
2020-11-03 15:04:49.122 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 216 - Msg: ***** Creating directory directory (DOCUMENTS/private/books)
2020-11-03 15:04:49.123 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 259 - Msg: ***** Done adding books directory
2020-11-03 15:04:49.123 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 216 - Msg: ***** Creating directory directory (DOCUMENTS/private/modules)
2020-11-03 15:04:49.124 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 261 - Msg: ***** one adding modules directory
2020-11-03 15:04:49.124 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 244 - Msg: ***** Reading directory (DOCUMENTS/private)
2020-11-03 15:04:49.126 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 263 - Msg: ***** Read private folder: 
2020-11-03 15:05:43.822 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 203 - Msg: ***** Creating directory directory (DOCUMENTS/private)
2020-11-03 15:05:43.823 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 257 - Msg: ***** Done adding private directory
2020-11-03 15:05:43.823 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 216 - Msg: ***** Creating directory directory (DOCUMENTS/private/books)
2020-11-03 15:05:43.824 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 259 - Msg: ***** Done adding books directory
2020-11-03 15:05:43.824 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 216 - Msg: ***** Creating directory directory (DOCUMENTS/private/modules)
2020-11-03 15:05:43.824 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 261 - Msg: ***** one adding modules directory
2020-11-03 15:05:43.824 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 244 - Msg: ***** Reading directory (DOCUMENTS/private)
2020-11-03 15:05:43.825 3240-3240/com.myapp.app I/Capacitor/Console: File: http://192.168.5.101:8100/main.js - Line 263 - Msg: ***** Read private folder: 

Notice the first attempt at creating the main ‘private’ directory does not return any errors.
Then when I have the code do a second check to see if the directories need to be made again I get the same result: no error, no created directories…

How can I figure out what’s happening here, and get it to work?
Any ideas?..

Just so that we’re speaking the same language, please read this post describing three types of functions. mkdir returns a Promise, which you’re ignoring. That’s totally a cool thing to do as long as you’re writing “type A” functions, where nobody cares when it does the work. However, you do seem to care when it finishes, which means that you need to pay attention to the return value from mkdir and propagate it appropriately. Explicitly declaring types for the return value of every function I write helps me personally here as well. A casual reader (and your toolchain) can’t know by instant inspection whether makePrivateDirectory is going to tell us anything about when it’s finished.

1 Like

Thanks very much for the good advice.
Sadly, I do not have a lot of experience playing with Promises.

I tried doing something like this:

public makePrivateDirectory() {
    console.log('***** Creating directory directory ('+FilesystemDirectory.Documents+'/private)');
    return new Promise((resolve, reject) => {
      Filesystem.mkdir({
        path: 'private',
        directory: FilesystemDirectory.Documents,
        recursive: false,
      });
      resolve();
		});
	}
  async createSystemDirectories() {
    this.makePrivateDirectory().then( () => {
      console.log('***** Done adding private directory');
      this.makeDirectory('books');
      console.log('***** Done adding books directory');
      this.makeDirectory('modules');
      console.log('***** Done adding modules directory');
    }).then(() => {
      let ls = this.readdir('private');
      console.log('***** Read private folder: ', ls);
    });
  }

This gave me no different result…

I should have also mentioned before:

  • I took the previous code directly from the documentation site
  • and it works perfectly fine when I use ‘ionic serve’ or when I build for electron.
  • It’s only with the android emulator that this is failing…

I’m sorry to say it, but there isn’t any way around this. You absolutely need to get fully grounded in the reactive and functional thinking that permeates web app authorship. I also came from an imperative programming mindset, where I always think I’m in control of the flow of execution: do this, do that, then do that other thing. That’s not how web apps work.

No. You virtually never want to be manually instantiating Promises.

Think backwards, from the smallest building block out. Referring again to my taxonomy of asynchronous functions, everything here is a class C: the caller cares when the work finishes.

Let’s write makeDirectory, which I’ll rename makePrivateSubdirectory to make it more clear what it’s doing. We’re committed to declaring return types for every function we write, and makePrivateSubdirectory must tell us when it’s finished, so it has to be shaped like so:

makePrivateSubdirectory(path: string): Promise<void> {
}

Before makePrivateSubdirectory can do anything, it needs that private parent directory to exist. In practice, it would be a whole lot easier to just use that recursive parameter to have mkdir do this for us, but to explicate how to write these things, I’m going to pretend that doesn’t exist.

So we also need this function:

ensurePrivateDirectory(): Promise<void> {
  // there are a couple of ways to implement this:
  // you could just call `mkdir` and handle the case where it already exists
  // you could call `stat` on it to see if it exists first
}

…now we can flesh out makePrivateSubdirectory:

makePrivateSubdirectory(path: string): Promise<void> {
  // class C function: first word must be "return"
  return this.ensurePrivateDirectory()
    .then(() => Filesystem.mkdir({...}))
    .then(() => {}); // elide out `mkdir`'s `MkdirResult`
}

…and we can do a bunch of them at once:

makePrivateSubdirectories(paths: string[]): Promise<void> {
  return Promise.all(paths.map(path => this.makePrivateSubdirectory(path)))
    .then(() => {});
}

Now your createSystemDirectories can just be:

createSystemDirectories(): Promise<void> {
  return this.makePrivateSubdirectories(["books", "modules"]);
}
1 Like

That is a very well written (and massively appreciated) response!
I’ll take a swing at this later tonight and let you know how it goes.

You’re right that this will take a different way of looking at things, but I like where it’s taking things.

Thanks again!

OK, I’ve had a chance to test this out. But I’m not getting any different result.

Here is what my new code looks like:

  ensurePrivateDirectory(): Promise<MkdirResult> {
    let exists = Filesystem.stat({ path: 'checheza', directory: FilesystemDirectory.Documents });
    if (!exists) {
      console.log('***** Main directory does not exist yet');
      return Filesystem.mkdir({ path: 'checheza', directory: FilesystemDirectory.Documents, recursive: false, });
    }else {
      console.log('***** Main directory exists already:', exists);
      return exists;
    }
  }

  makePrivateSubdirectory(path: string): Promise<void> {
    return this.ensurePrivateDirectory()
      .then(() => Filesystem.mkdir({ path: 'checheza/'+path, directory: FilesystemDirectory.Documents, recursive: false, }))
      .then(() => {});
  }

  makePrivateSubdirectories(paths: string[]): Promise<void> {
    return Promise.all(paths.map(path => this.makePrivateSubdirectory(path)))
      .then(() => {});
  }

  createSystemDirectories(): Promise<void> {
    return this.makePrivateSubdirectories(["books", "modules"]);
  }

Any further suggestions?

You are using the web version of the plugin, not the native version of the plugin.

Instead of import { Filesystem, FilesystemDirectory, FilesystemEncoding } from '@capacitor/core'; do

import { FilesystemDirectory, FilesystemEncoding, Plugins } from '@capacitor/core';
const { Filesystem } = Plugins;
1 Like

Thanks jcesarmobile, but when I make that change I get the following error:

$ ionic build
> ng run app:build

ERROR in src/app/core.service.ts:5:24 - error TS2552: Cannot find name 'Plugins'. Did you mean 'Plugin'?

5 const { Filesystem } = Plugins;
                         ~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:11727:13
    11727 declare var Plugin: {
                      ~~~~~~
    'Plugin' is declared here.

check the @capacitor/core import change too, it should import Plugins

Thanks very much @jcesarmobile, that fixed that issue.
But it still doesn’t look to be creating the folders.

  ensurePrivateDirectory(): Promise<MkdirResult> {
    let exists = Filesystem.stat({ path: 'private', directory: FilesystemDirectory.Documents });
    if (!exists) {
      console.log('***** Main directory does not exist yet');
      return Filesystem.mkdir({ path: 'private', directory: FilesystemDirectory.Documents, recursive: false, });
    }else {
      console.log('***** "private" directory exists already:', exists);
      return exists;
    }
  }

From a clean install in the emulator that IF statement above always returns false:

2020-11-06 10:40:19.516 6359-6359/com.myapp.app I/Capacitor/Console: File: http://localhost/main-es2015.js - Line 239 - Msg: ***** "private" directory exists already:

Notice how the exists variable looks to return as NULL

What am i doing wrong here?
Thanks again for both your clear help.

All capacitor plugin methods return promises, on the stat call you are not handling the promise properly, you need to await it or use then

1 Like

Thank you. Reading, learning, and trying.
Will let you know how it goes.

OK. Thank you again for the reading material. That was indeed a very good tutorial.
Sadly, things are still not working…

Code snippets below.

app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { Zip } from '@ionic-native/zip/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';


@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule,IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    Zip,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

core.service.ts

import { Injectable } from '@angular/core';

import { FilesystemDirectory, Plugins, MkdirResult } from '@capacitor/core';

import { Zip } from '@ionic-native/zip/ngx';

// import { Zip } from '@ionic-native/zip';

const { Filesystem } = Plugins;

declare var require: any;

@Injectable({

providedIn: 'root'

})

export class CoreService {

constructor(private zip: Zip) {}
  
  ensurePrivateDirectory(): Promise<MkdirResult> {
    // dumming this method down for the sake of testing
      console.log('***** Main directory does not exist yet');
      return Filesystem.mkdir({ path: 'private', directory: FilesystemDirectory.Documents, recursive: false, });
  }

  makePrivateSubdirectory(path: string): Promise<void> {
    return this.ensurePrivateDirectory()
      .then(() => Filesystem.mkdir({ path: 'private/'+path, directory: FilesystemDirectory.Documents, recursive: false, }))
      .then(() => {});
  }

  makePrivateSubdirectories(paths: string[]): Promise<void> {
    return Promise.all(paths.map(path => this.makePrivateSubdirectory(path)))
      .then(() => {});
  }

  createSystemDirectories(): Promise<void> {
    return this.makePrivateSubdirectories(["books", "modules"])
    .then(() => {
      Filesystem.readdir({
        path: 'private',
        directory: FilesystemDirectory.Documents
      })
      .then((ls) => console.log("***** System Files Created.  Read: ", ls)).then(() => {});
    }).then(() => {});
  }
}

LogTrace:

2020-11-11 13:21:06.700 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/main-es2015.js - Line 235 - Msg: ***** Main directory does not exist yet
2020-11-11 13:21:55.436 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-module-module-es2015.js - Line 144 - Msg: ***** Going to /list-modules/mymodule
2020-11-11 13:21:59.692 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/main-es2015.js - Line 222 - Msg: ***** Reading directory (DOCUMENTS/private)
2020-11-11 13:21:59.706 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/main-es2015.js - Line 235 - Msg: ***** Main directory does not exist yet
2020-11-11 13:21:59.711 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-details-module-details-module-es2015.js - Line 146 - Msg: ***** Going to download zip
2020-11-11 13:21:59.715 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-details-module-details-module-es2015.js - Line 159 - Msg: ***** Request sent!
2020-11-11 13:21:59.804 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-details-module-details-module-es2015.js - Line 162 - Msg: ***** Response header received!
2020-11-11 13:22:00.267 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-details-module-details-module-es2015.js - Line 170 - Msg: ***** :-) Done!
2020-11-11 13:22:00.267 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/main-es2015.js - Line 222 - Msg: ***** Reading directory (DOCUMENTS/private/modules)
2020-11-11 13:22:00.270 6877-6877/com.myapp.app I/Capacitor/Console: File: http://localhost/module-details-module-details-module-es2015.js - Line 172 - Msg: ***** Contents of modules folder:

The real issue I’m seeing/having here is that I cannot see any contents from my readdir() command.
Based on this output, it looks like nothing is ever getting written. But I’m also not getting any errors…
Notice the new bit I added to the end of the createSystemDirectories() method that logs out the contents of the “private” folder. And then notice that that output NEVER gets printed to the log.

So, yeah, clearly I’m still doing something wrong here.
Again, I thank you all for any assistance you can provide.

P.S. Here is my package.json file in case it helps:

{
  "name": "my_app",
  "version": "0.0.1",
  "author": "Me",
  "homepage": "https://myapp.com/",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~10.0.0",
    "@angular/core": "~10.0.0",
    "@angular/forms": "~10.0.0",
    "@angular/platform-browser": "~10.0.0",
    "@angular/platform-browser-dynamic": "~10.0.0",
    "@angular/router": "~10.0.0",
    "@capacitor/android": "2.4.2",
    "@capacitor/core": "2.4.2",
    "@ionic-native/core": "^5.0.0",
    "@ionic-native/splash-screen": "^5.0.0",
    "@ionic-native/status-bar": "^5.0.0",
    "@ionic-native/zip": "^5.0.0",
    "@ionic/angular": "^5.0.0",
    "rxjs": "~6.5.5",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.1002.0",
    "@angular/cli": "~10.0.5",
    "@angular/compiler": "~10.0.0",
    "@angular/compiler-cli": "~10.0.0",
    "@angular/language-service": "~10.0.0",
    "@capacitor/cli": "2.4.2",
    "@ionic/angular-toolkit": "^2.3.0",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.11.1",
    "codelyzer": "^6.0.0",
    "cordova-android": "^9.0.0",
    "cordova-plugin-device": "^2.0.2",
    "cordova-plugin-ionic-keyboard": "^2.2.0",
    "cordova-plugin-ionic-webview": "^4.2.1",
    "cordova-plugin-splashscreen": "^5.0.2",
    "cordova-plugin-statusbar": "^2.4.2",
    "cordova-plugin-whitelist": "^1.3.3",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~3.3.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~3.9.5"
  },
  "description": "MyApp",
  "cordova": {
    "plugins": {
      "cordova-plugin-whitelist": {},
      "cordova-plugin-statusbar": {},
      "cordova-plugin-device": {},
      "cordova-plugin-splashscreen": {},
      "cordova-plugin-ionic-webview": {
        "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
      },
      "cordova-plugin-ionic-keyboard": {}
    },
    "platforms": [
      "android"
    ]
  }
}

P.P.S. As you can see, I’ve made the conversion from Cordova to Capacitor. So as an extra special request: feel free to let me know if I need to or should remove any/all of these Cordova libraries. Thanks again.

This response is depreciated and not functional anymore, in order to use it on Capacitor 3 i would recommend using

import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
// FilesystemDirectory is now "Directory" only.