Skip to content
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

FileReader API not firing #1564

Closed
5 tasks
planesandunicorns opened this issue May 23, 2019 · 42 comments
Closed
5 tasks

FileReader API not firing #1564

planesandunicorns opened this issue May 23, 2019 · 42 comments

Comments

@planesandunicorns
Copy link

planesandunicorns commented May 23, 2019

Description of the problem:
In my project I'm trying to read and convert a Blob downloaded from the dropbox api to a text string. When building my project using Cordova, the FileReader fires onload and onprogress events. When building using Capacitor, filereader does not fire any events at all.

Affected platform

  • Android
  • [x ] iOS
  • electron
  • web

OS of the development machine

  • Windows
  • [ x] macOS
  • linux

Other information:
As suggested in other bug reports, I tried wrapping the FileReader in

platform.ready().then(_ => {
/// perform file reading 
})

and in

zone.run(() => {
/// perform file reading
})

None helped. Cordova has no issues reading the file.

When I use

var text = await (new Response(response.fileBlob)).text();

to read the file blob, it does work.

Capacitor version: 1.0.0-beta.24

@jcesarmobile
Copy link
Member

Can you provide a sample project?

@planesandunicorns
Copy link
Author

Okay, so in the sample project it works. Guess the problem lies somewhere in the use of dropbox combined with promises and the filereader. I shall investigate further and when I find something I'll let you know!

@planesandunicorns
Copy link
Author

@jcesarmobile, apparently the problem is in the cordova-plugin-file plugin.
https://github.com/ronwondaal/capacitorFileReaderTest

@jcesarmobile
Copy link
Member

But on the plugin itself or only when using it with Capacitor?

@planesandunicorns
Copy link
Author

When using it with capacitor it prevents FileRead from firing events.

@jcesarmobile
Copy link
Member

Looks like some problem with zone.js, but not sure why it only affects Capacitor and not Cordova

If I log the FileReader object I get this results:

Capacitor with Ionic {"__zone_symbol__originalInstance":{"_readyState":1,"_error":null,"_result":null,"_progress":0,"_localURL":"","_realReader":{}}}

Cordova with Ionic {"_readyState":1,"_error":null,"_result":null,"_progress":0,"_localURL":"","_realReader":{"__zone_symbol__originalInstance":{}}}

Capacitor without framework {"_readyState":1,"_error":null,"_result":null,"_progress":0,"_localURL":"","_realReader":{}}

Cordova without framework {"_readyState":1,"_error":null,"_result":null,"_progress":0,"_localURL":"","_realReader":{}}

The onload is being added to the FileReader object, but the problem is, when using Capacitor with zone.js, the FileReader object is put inside the "__zone_symbol__originalInstance" object and the onloadis set to the object containing the "__zone_symbol__originalInstance" and not on the real FileReader.

If we take zone.js (angular/ionic) out of the equation it works fine.

@jcesarmobile
Copy link
Member

Found this ionic-native issue danielsogl/awesome-cordova-plugins#505

Now I know why this isn't an issue on Cordova, polyfills.js is loaded there before cordova.js, but in Capacitor cordova.js is injected into the webview before polyfills.js.
I've tried injecting polyfills.js too but that doesn't seem to work.

@planesandunicorns
Copy link
Author

Yeah, I took a look at the angular zone.js file, and it seems the have done some bug fixes regarding cordova. The also have zone.js patch for cordova here: https://github.com/angular/zone.js/blob/master/dist/zone-patch-cordova.js
But I'm not that skilled a programmer to be able to figure out what they are doing to patch the stuff :(

@digaus
Copy link
Contributor

digaus commented Aug 1, 2019

@jcesarmobile
Is someone looking for a fix? Currently also running into this issue while switching from cordova to capacitor...

Edit: this worked for me danielsogl/awesome-cordova-plugins#505 (comment)

@dansterrett
Copy link

dansterrett commented Sep 18, 2019

Found this ionic-native issue ionic-team/ionic-native#505

Now I know why this isn't an issue on Cordova, polyfills.js is loaded there before cordova.js, but in Capacitor cordova.js is injected into the webview before polyfills.js.
I've tried injecting polyfills.js too but that doesn't seem to work.

@jcesarmobile, thanks for the tip. Loading polyfills.js before cordova.js fixed the issue for me.

EDIT: Loading pollyfills.js before cordova.js fixed this problem, but caused other problems. The best solution was to load pollyfills.js AFTER cordava.js, but BEFORE cordova_plugins.js (and all the other cordova plugin javascript files), which get injected into the Webview somewhere near the bottom of the element.

@tripodsgames
Copy link

tripodsgames commented Oct 4, 2019

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

@neerajsaxena0711
Copy link

I can confirm @tripodsgames 's solution is working for me.

@tgangso
Copy link

tgangso commented Dec 11, 2019

Thank you for the workaround @tripodsgames saved me.

Maybe time for an official fix?

@lampham2505
Copy link

lampham2505 commented Dec 20, 2019

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

This solution's working for me too, but after using it, I can see that the UI's loading slower than before(it's now display the text "back" then change to the icon). Does anyone else get that bad peformance or just me?

@supinder-cf
Copy link

@tripodsgames 's solution worked like charm. Thank you so much.

@jcesarmobile
Copy link
Member

closing since this is a zone.js bug related to changing cordova-plugin-file's FileReader.
If you are affected by this, use the workaround proposed by tripodsgames

@casper5822
Copy link

casper5822 commented May 4, 2020

I have this problem with Ionic V4 and capacitor without the cordova-plugin-file, use just a FileReader object and the problem is still present.

If you use the hack, it works but there is a very bad performance, i made tests in a galaxy s7 edge:
to have better performance i found this solution, use the NgZone:

import { NgZone } from "angular/core";

constructor(private zone:NgZone) {}

 this.zone.run(()=>{

      const reader = new FileReader();
      reader.onload = () => { do stuffs with images };

 });
``

@SamTomashi
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

I have been hitting my head for almost a week now over this issue using Ionic 6, and finally this solution came to the rescue.

@mauryaak15
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

You saved my life bro, thanks a ton

@ahmad-alk
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

You saved someone's else life here as well, Thanks man I waste a lot of time on this.

@deborahjames
Copy link

deborahjames commented Jul 24, 2020

@tripodsgames Thanks!!!

Adding a few more lines - FileReader onload not geeting called in android SOLVED

export function getFileReader(): FileReader {
  const fileReader = new FileReader();
  const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
  return zoneOriginalInstance || fileReader;
}


onSelectFile(event) {
    if (event.target.files && event.target.files[0]) {
      let newInstance = getFileReader();
      newInstance.readAsDataURL(event.target.files[0]); 
      newInstance.onload = (imgsrc) => {               
        let url = (imgsrc.target as FileReader).result; 
        console.log(url)
      }
    }
  }

@moridianmess
Copy link

moridianmess commented Aug 11, 2020

If you have plugins that rely on FileReader working correctly, using snippets from the getFileReader() code mentioned above, I have a solution

Add to app.module.ts:

export class FileReaderA extends window.FileReader {
	constructor() {
		super();
		const zoneOriginalInstance = (this as any)['__zone_symbol__originalInstance'];
		return zoneOriginalInstance || this;
	}
}

window.FileReader = FileReaderA;

or add the above code to another file and import the file, not the class.

This is a hack but it means that when calling new FileReader() all the events work. Ideally it would be nice to be able to control the order in which scripts are imported. As the fix I've always used in a cordova based project is to change the order of polyfills.js and cordova.js in the index.html file.

@mtozzi
Copy link

mtozzi commented Oct 2, 2020

private getFileReader(): FileReader {
const fileReader = new FileReader();
const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

Worked for me after days searching for a solution! Tks @tripodsgames !

@rajanwalt
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

It's working to me. Thanks a lot!

@rohitsethi007
Copy link

If you have plugins that rely on FileReader working correctly, using snippets from the getFileReader() code mentioned above, I have a solution

Add to app.module.ts:

export class FileReaderA extends window.FileReader {
	constructor() {
		super();
		const zoneOriginalInstance = (this as any)['__zone_symbol__originalInstance'];
		return zoneOriginalInstance || this;
	}
}

window.FileReader = FileReaderA;

or add the above code to another file and import the file, not the class.

This is a hack but it means that when calling new FileReader() all the events work. Ideally it would be nice to be able to control the order in which scripts are imported. As the fix I've always used in a cordova based project is to change the order of polyfills.js and cordova.js in the index.html file.

Thanks to you and @tripodsgames the fix saved me, took me 4 days to finally come across this solution and it works!

@nicolas-gameffecive
Copy link

Thanks @tripodsgames
Your solution saved my day

@davidst2017
Copy link

BTW if you the change detection takes to long call detectChanges() from ChangeDetecotrRef

FYI import ChangeDetectorRef from "@angular/core"

@patriciopage
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

Thanks @tripodsgames , this is IT.

@MrWeezle
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

still saving lives and nerves even after almost a year... Thanks man

@Bforsyth1234
Copy link

@tripodsgames Thank you!!!!

Dear google, please rank this higher in search results..... k thanks.

@fromage9747
Copy link

fromage9747 commented Apr 24, 2021

export function getFileReader(): FileReader {
const fileReader = new FileReader();
const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
return zoneOriginalInstance || fileReader;
}

Thank you so much! I had been fighting with this issue for days. This resolved it! Thank you thank you thank you!

Ionic Version: 6.13.1 On Android App

@markbeij
Copy link

If you have plugins that rely on FileReader working correctly, using snippets from the getFileReader() code mentioned above, I have a solution

Add to app.module.ts:

export class FileReaderA extends window.FileReader {
	constructor() {
		super();
		const zoneOriginalInstance = (this as any)['__zone_symbol__originalInstance'];
		return zoneOriginalInstance || this;
	}
}

window.FileReader = FileReaderA;

or add the above code to another file and import the file, not the class.

This is a hack but it means that when calling new FileReader() all the events work. Ideally it would be nice to be able to control the order in which scripts are imported. As the fix I've always used in a cordova based project is to change the order of polyfills.js and cordova.js in the index.html file.

Thank you very much, worked for me!

@PhilippKorn
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

im a nooby, where to I have to put that code? Into the FileReader file or my ts file?

@moridianmess
Copy link

You can try this.

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

im a nooby, where to I have to put that code? Into the FileReader file or my ts file?

Using that method you need to use that function instead of using FileReader normally. If you use the method I suggested it means you can use it normally.

@luqmanyusof
Copy link

I endup using "Capacitor/Filesystem" library to read the file that was written by using "@ionic-native/file/ngx".

@mherbert71
Copy link

If you have plugins that rely on FileReader working correctly, using snippets from the getFileReader() code mentioned above, I have a solution

Add to app.module.ts:

export class FileReaderA extends window.FileReader {
	constructor() {
		super();
		const zoneOriginalInstance = (this as any)['__zone_symbol__originalInstance'];
		return zoneOriginalInstance || this;
	}
}

window.FileReader = FileReaderA;

or add the above code to another file and import the file, not the class.

This is a hack but it means that when calling new FileReader() all the events work. Ideally it would be nice to be able to control the order in which scripts are imported. As the fix I've always used in a cordova based project is to change the order of polyfills.js and cordova.js in the index.html file.

this is the way... great solution, thanks!

@andyyapwl
Copy link

andyyapwl commented Apr 3, 2022

If you have plugins that rely on FileReader working correctly, using snippets from the getFileReader() code mentioned above, I have a solution

Add to app.module.ts:

export class FileReaderA extends window.FileReader {
	constructor() {
		super();
		const zoneOriginalInstance = (this as any)['__zone_symbol__originalInstance'];
		return zoneOriginalInstance || this;
	}
}

window.FileReader = FileReaderA;

or add the above code to another file and import the file, not the class.

This is a hack but it means that when calling new FileReader() all the events work. Ideally it would be nice to be able to control the order in which scripts are imported. As the fix I've always used in a cordova based project is to change the order of polyfills.js and cordova.js in the index.html file.

Kudos to you and @tripodsgames! It actually drained me many many days to trace and this amazing patching is really proven and 100% working!

chrisekelley added a commit to Tangerine-Community/Tangerine that referenced this issue Apr 6, 2022
@lalithumation
Copy link

Can Any one help m when i and Passing data to File instance the data get changes in IOS and Android

in Android:-
lastModified: 1649763166169
lastModifiedDate: Tue Apr 12 2022 17:02:46 GMT+0530 (India Standard Time) {}
name: "test"
size: 118514
type: "image/jpeg"
webkitRelativePath: ""

in IOS :-
end: 0
lastModified: null
lastModifiedDate: null
localURL: "test"
name: [Uint8Array] (1)
size: 0
start: 0
type: {type: "image/jpeg"}

i want same as android in IOS

@Umer281
Copy link

Umer281 commented Jun 27, 2022

@tripodsgames thanks it is working

@mariusmoessmer
Copy link

mariusmoessmer commented Jul 1, 2022

The above provided solution regarding the __zone_symbol__originalInstance - filereader works but has problems with changedetection in angular since the onload method of the filereader gets executed outside angular-zone.

We have overcome this issue by implementing a new filereader that decorates the __zone_symbol__originalInstance - filereader and executes the onload-method within the ngZone.

Therefore, we extend the above provided solution by following code:

export class RealFileReader extends window.FileReader implements FileReader {
	constructor() {
		super();
		const zoneOriginalInstance =
			// eslint-disable-next-line @typescript-eslint/naming-convention
			(this as any as { __zone_symbol__originalInstance?: FileReader | undefined } & FileReader).__zone_symbol__originalInstance;

		return new DecoratedFileReader(zoneOriginalInstance || this, window.globalInjector.get(NgZone));
	}
}

window.FileReader = RealFileReader;

export function appInit(injector: Injector) {
	// eslint-disable-next-line arrow-body-style
	(window as any).globalInjector = injector;
	return (): Promise<any> => Promise.resolve();
}
import { NgZone } from '@angular/core';


/**
 * decorates a real filereader and executes all functions like onload within NgZone
 */
export class DecoratedFileReader implements FileReader {

	/**
	 *
	 */
	constructor(private realFileReader: FileReader, private ngZone: NgZone) {
	}

	// eslint-disable-next-line @typescript-eslint/naming-convention
	public get DONE(): number {
		return this.realFileReader.DONE;
	}
	// eslint-disable-next-line @typescript-eslint/naming-convention
	public get EMPTY(): number {
		return this.realFileReader.EMPTY;
	}
	// eslint-disable-next-line @typescript-eslint/naming-convention
	public get LOADING(): number {
		return this.realFileReader.LOADING;
	}

	public get error(): DOMException | null {
		return this.realFileReader.error;
	}

	public get readyState(): number {
		return this.realFileReader.readyState;
	}

	public get result(): string | ArrayBuffer | null {
		return this.realFileReader.result;
	}

	public get onabort(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onabort;
	}

	public set onabort(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onabort = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onabort = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}

	// eslint-disable-next-line @typescript-eslint/member-ordering
	public get onerror(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onerror;
	}

	public set onerror(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onerror = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onerror = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}

	// eslint-disable-next-line @typescript-eslint/member-ordering
	public get onload(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onload;
	}

	public set onload(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onload = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onload = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}

	// eslint-disable-next-line @typescript-eslint/member-ordering
	public get onloadend(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onloadend;
	}

	public set onloadend(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onloadend = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onloadend = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}

	// eslint-disable-next-line @typescript-eslint/member-ordering
	public get onloadstart(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onloadstart;
	}

	public set onloadstart(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onloadstart = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onloadstart = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}
	// eslint-disable-next-line @typescript-eslint/member-ordering
	public get onprogress(): ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null {
		return this.realFileReader.onprogress;
	}

	public set onprogress(value: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null) {
		if (value == null) {
			this.realFileReader.onprogress = value;
		} else {
			// eslint-disable-next-line no-unused-vars
			this.realFileReader.onprogress = ev => {
				this.ngZone.run(() => value.call(this.realFileReader, ev));
			};
		}
	}

	abort(): void {
		this.realFileReader.abort();
	}
	readAsArrayBuffer(blob: Blob): void {
		this.realFileReader.readAsArrayBuffer(blob);
	}
	readAsBinaryString(blob: Blob): void {
		this.realFileReader.readAsBinaryString(blob);
	}
	readAsDataURL(blob: Blob): void {
		this.realFileReader.readAsDataURL(blob);
	}
	readAsText(blob: Blob, encoding?: string): void {
		this.realFileReader.readAsText(blob, encoding);
	}

	addEventListener<K extends keyof FileReaderEventMap>(
		type: K,
		listener: (this: FileReader, ev: FileReaderEventMap[K]) => any, options?: boolean | AddEventListenerOptions
	): void;
	addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
	addEventListener(type: any, listener: any, options?: any): void {
		return this.realFileReader.addEventListener(type, listener, options);
	}

	removeEventListener<K extends keyof FileReaderEventMap>(
		type: K,
		listener: (this: FileReader, ev: FileReaderEventMap[K]) => any, options?: boolean | EventListenerOptions
	): void;
	removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
	removeEventListener(type: any, listener: any, options?: any): void {
		return this.realFileReader.removeEventListener(type, listener, options);
	}

	dispatchEvent(event: Event): boolean {
		return this.ngZone.run(() => this.realFileReader.dispatchEvent(event));
	}
}

@harinderratton
Copy link

export function getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
}
...
let newInstance = getFileReader();

worked like a charm

@ionitron-bot
Copy link

ionitron-bot bot commented Nov 10, 2022

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Nov 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests